合并 master 整合(棋盘瓦片与立体表现重做批): 冲突三件解决+封印呈现对齐迭代56+卡片第六轮复核

- art-bible 冲突:保留 C 地形八条,采纳 master v3.48 封印规则并适配——封印居已揭示地面格心、身份字形垫色牌叠于法阵上、印记与色牌固定低于小人、不参与浮空层;承载面因新地面 #695038 对封印黑仅 2.35–2.98:1,改本关受光棱色封印石(6.45–9.94:1,零新增色);版本 v3.51;§1.2/§1.4/§3.2/§4.1/§6.5/§8.2 同步。
- README 冲突:保留 master 迭代56「结算期零逐行文字与 FX 排播链保持」+旧地形行(父注保留),追加本批 6 条新能力;能力名改「旧瓦片退役与普通数字零底牌」「棋盘顶面锚点与迷雾几何(微光、变异岩顶偏移,封印格心)」。
- roadmap 冲突:保留 master 迭代55/56当前位原文,仅把待编池「二三关棋盘观感」替换为「棋盘瓦片与立体表现重做(已写卡,待编排)」。
- 需求卡片适配:封印格色牌为唯一例外、plate 保留(CellUnit 12→13)、board_terrain_layer 增 sealed_at(≤13 成员)、测试改 _test_no_ordinary_plate_path+_test_seal_substrate、§12 行8/9/11/14/15 与 §16 第六轮 6 条记录;术语表/checklist/battle.md/待办/08 同步 v3.51。
- 区内凭据:pipeline_gate PASS(条目45/总表56/交叉核3/卡片5);capability_audit 红项0(能力行123/套件87);run_all_gates --docs 内容无新增红(回执待本提交后重跑)。
This commit is contained in:
DKChan committed 2026-11-07 23:39:53 +08:00
commit 5b42c13aca
165 files changed
+3342 -1197

No files matched your search

+3 -3
View File
@@ -44,11 +44,11 @@
| 批次开工 / 流程纪律 | `design/engineering/process-conventions.md`——车道铁律(一切改动走独立 worktree,主区只作合并落点)、合回后远端 CI、五段流水线、批次清单、改完即测与 MC/闸机细则、像素纪律 |
| 流水线段入口 | 技能对照——doc-design=文档设计(兼任反哺点落文)/asset-ingest=美术录入/req-card=写卡/iter-plan=迭代编排/iter=实现(五者均装于项目 `.zcode/skills/`);登记不立技能;批后收尾与中间文档退役=batch-closure(同一目录);格式与状态机权威=`design/engineering/doc-conventions.md` §12 |
| 定代码方案 / 写代码 / 改代码前 | `design/engineering/code-conventions.md`——结构/契约/语言/数值/流程五类硬纪律(反例库 = 同目录 code-conventions-cases.md) |
| 定测试方案 / 写测试 / 改测试前 | `design/engineering/test-conventions.md`——探针标配 / 回归断言 / 提交前验证 / 验收清单落码 |
| 定测试方案 / 写测试 / 改测试前 | `design/engineering/test-conventions.md`——探针标配 / 回归断言 / 提交前验证 / 验收清单落码;测试工具手册与闸机操作=`game/tests/README.md` |
| 定文档方案 / 写文档 / 改文档前 | `design/engineering/doc-conventions.md`——白话写作 / 载体分工 / 用语 / 结构骨架 / 会话记录与生命周期 |
| 概念切分与互链 | `design/engineering/concept-conventions.md`——概念封装 / 跨文档引用 / 归档与事实源 |
| 系统设计 / 增删文档 | `design/gdd/02-系统清单.md`——根索引 + 中文规范名↔英文代号对照 |
| 架构(唯一事实源) | `design/engineering/architecture/architecture.md`——历史决策沿革查 git commit log,不另设决策文档 |
| 架构(唯一事实源) | `design/engineering/architecture/architecture.md`(只写约束面:分层/模块归属/数据流/API 边界;实现现状归代码与头注,闸清单归 `game/tests/README.md`)——历史决策沿革查 git commit log,不另设决策文档;修订须用户发令 |
| 术语与已锁数值 | 规范名与定义 = `design/gdd/术语表.md`(全库唯一对照表,仅 owner 文档可修订);现行数值 = `game/data/tables/*.tres`;规则(概念面)= 各系统 owner 文档;取值域与字段契约 = `game/data/schemas/*.gd`。冲突须显式提出,禁默默改值 |
| 现在做什么 / 待办与待裁 | 根 `roadmap.md` 顶部「当前位」节+迭代总表六列 · 待办唯一登记处 `待办清单.md` · 批次过程与结论查 git 提交信息与 `reports/` |
@@ -122,7 +122,7 @@
- 文件头 = `##` 文档注释,介绍本模块是什么;禁写变更叙事(改动史查 git log);禁文档反向链接(code-conventions §1.4.1)
- 数值单一事实源——玩法数值只存 `data/tables/`(.tres)经 ConfigDB 单点读取;禁数值字面量与自建 `load()` 表路径
- RNG 单点——禁 `randomize()`/自建 RandomNumberGenerator,随机只经 `RngService` 统一流
- 架构事实唯一源——架构事实只认 `design/engineering/architecture/architecture.md`;禁立第二架构文档或决策文档体系,历史决策记录只进 git commit log
- 架构事实唯一源——架构事实只认 `design/engineering/architecture/architecture.md`(只写约束面:分层/模块归属/数据流/API 边界;实现现状归代码与头注);禁立第二架构文档或决策文档体系,历史决策记录只进 git commit log;架构文档写权限=用户专属,agent 与技能只读(2026-11-07 用户裁)
- Core 层零 UI 依赖——完整战斗必须可 `--headless --script` 跑通;fail-fast——数据校验错误一律致命中止,无软回退
- spec-AC 一致性——规约与验收标准禁止在同一输入上给出不同答案;评审隔离——`/design-review` 必须独立会话跑(与 `/design-system` 隔离)
- 像素结构件——先抄后造、验收写成像素级判据、±1px 坐标推导(process-conventions 第 7 节)
+2 -2
View File
@@ -27,7 +27,7 @@
|---|---|
| 一句话视觉规则 | 明快的地牢,轻快的扫荡,仔细的推理——**明亮像素幻想,全年龄,非暗黑**,角色 2~3 头身 |
| 原则一「预告上格」 | 格内绘制优先级 **浮空数字与引导线(最顶)> 小人 > 封印印记 > 交互反馈 > 变异纹带 > 装饰变体 > 底色**;北侧贴花低于小人、南侧随岩体在前;任何元素禁止遮挡浮空数字与引导线;类型微光为底效泛光层,不入遮挡栈 |
| 原则二「明亮为皮,明度分档为骨」 | 暖倾向;浮空数字暖白芯+2px 深墨描边(art-bible §3.2/§4.1);色牌退役;未开格底亮度地板与饱和上限同 §4.1(门值定值面=互辨抽测件) |
| 原则二「明亮为皮,明度分档为骨」 | 暖倾向;浮空数字暖白芯+2px 深墨描边(art-bible §3.2/§4.1);普通数字色牌退役(封印格身份字形色牌保留);未开格底亮度地板与饱和上限同 §4.1(门值定值面=互辨抽测件) |
| 原则三「一砖一世界」 | 变体优先换色板不加新资产;危险状态必须**形状+颜色双通道**(色弱灰度可辨) |
| 类型预读单通道(§4.6.3) | 未开格 = **类型常显微光**(五组色带 §4.2:芥末黄/黄绿/青/紫罗兰/绯红,色相+明度双码,避 300°–345° 禁入带,样张冻结);不呈现极性、不泄危险格与安全格(角嵌形状通道已不存在——微光格只留微光) |
| 剪影哲学(§5.1) | **所有角色自带轮廓通道:亮色描边+落影底盘**,识别不依赖背景;四类原型见各条目 |
@@ -43,7 +43,7 @@
### 1.1 格子样张(砖面形态定稿闸)⚙
- **要什么**:一张按 art-bible §1.4 参数画成的格子风格试验图。**内容清单与验收四项以 art-bible §1.4 为唯一定义处**(含类型微光呈现组),本条只登记生产视角差异:图类型色板变体组至少两套、样张含至少一处砖缘/岛缘厚度与一格装饰立牌示例(§3.2 呈现基准)。
- **风格/规格**:棋盘三关八色取 art-bible §4.1「三关棋盘环境八色」;语义岗 token 取 ui_kit 常量组(封印黑、危险红、奖励金、小人蓝;色牌退役)。
- **风格/规格**:棋盘三关八色取 art-bible §4.1「三关棋盘环境八色」;语义岗 token 取 ui_kit 常量组(封印黑、危险红、奖励金、小人蓝;普通数字色牌退役,封印格身份字形色牌保留)。
- **验收**:art-bible §1.4 四项按序判定(数字可读性 WCAG ≥4.5:1 硬指标 → 格态三形与微光互辨灰度可辨 → 焦点预算 → 图类型换皮抽测)。**通过 = 砖面形态与色板冻结**。
- **来源**:⚙ 脚本管线自产——样张是验收闸本身,必须按圣经参数画,无复用意义。
- **现状**:验收标准已成文,**测试未跑**。
+17 -16
View File
@@ -1,6 +1,6 @@
# 美术圣经(Art Bible)— Dungeon Sweeper
> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.50,历次修订明细见 git 提交史
> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.51,历次修订明细见 git 提交史
>
> **Art Director Sign-Off(AD-ART-BIBLE)**:历次批均未签核——此为 Lean/Solo 模式(非 PHASE-GATE)下的现行治理状态;full 门禁签核时机待用户表态。
> 风格基调:地牢题材但**非暗黑风**——轻快明亮的幻想冒险风,全年龄;像素风;角色 2~3 头身。
@@ -22,7 +22,7 @@
**原则一|预告上格**(取自 Into the Breach;锚定 P1 推理的掌控感、P2 规划的延迟兑现)
继承其格内信息分层与"攻击预告画在目标格上"的范式:一切威胁、伤害与结算预告必须直接绘制在雷图格本体上,禁止只进侧栏或悬浮提示;格上绘制优先级固定:**浮空数字与引导线(最顶层,永不被遮挡) > 小人标记 > 封印印记/类型微光 > 交互反馈(悬停/路径/可挖域微光) > 变异纹带 > 装饰变体 > 岩体顶面/地面底色**,任何元素禁止遮挡浮空数字与引导线。屏幕层序按脚底行分档:顶面贴花与交互反馈随所属格件 z——北侧(脚底≤小人)低于小人,南侧(脚底>小人)随岩体在前;信息优先级不变(北侧重叠时小人压过贴花)。格面类型预读 = 类型微光单通道,色相+明度双码(色带见 §4.2、键契约见 §4.6;徽章小图标族保留给库存与结算明细;类型微光为底效泛光层,不入遮挡栈、不遮任何元素)。与 ITB 之别:只取其信息骨架,弃其冷暗军事色调。验收:目检〈预告画在雷图格本体上,任何元素不遮挡浮空数字与引导线〉
继承其格内信息分层与"攻击预告画在目标格上"的范式:一切威胁、伤害与结算预告必须直接绘制在雷图格本体上,禁止只进侧栏或悬浮提示;格上绘制优先级固定:**浮空数字与引导线(最顶层,永不被遮挡) > 小人标记 > 封印印记/类型微光 > 交互反馈(悬停/路径/可挖域微光) > 变异纹带 > 装饰变体 > 岩体顶面/地面底色**,任何元素禁止遮挡浮空数字与引导线。屏幕层序按脚底行分档:顶面贴花与交互反馈随所属格件 z——北侧(脚底≤小人)低于小人,南侧(脚底>小人)随岩体在前;封印印记与封印格身份色牌固定低于小人(不参与南侧前推);信息优先级不变(北侧重叠时小人压过贴花)。格面类型预读 = 类型微光单通道,色相+明度双码(色带见 §4.2、键契约见 §4.6;徽章小图标族保留给库存与结算明细;类型微光为底效泛光层,不入遮挡栈、不遮任何元素)。与 ITB 之别:只取其信息骨架,弃其冷暗军事色调。验收:目检〈预告画在雷图格本体上,任何元素不遮挡浮空数字与引导线〉
*范围注:本原则约束"已存在的信息往哪里画",不新增信息种类;预告类信息以战斗侧 GDD 实际定义的为准(如 P2 已锁的结算来源标注),GDD 未定义的预告不因本条而存在。*
@@ -46,7 +46,7 @@
> 立意:扫雷数字可读性与暗色氛围相冲突,由一次格子样张测试定风格,具体形态由样张测试定稿。**样张** = 一张按本圣经参数画成的格子风格试验图,是棋盘地形形态(岩体+地面+浮空数字)的定稿闸——样张通过前,§3.2/§4.1 的一切参数只是候选,通过后冻结为制作标准。
**样张内容清单**(一图齐备,缺项即无效):未开格(合并岩体:岩顶大板+南/东崖面,含至少两种装饰变体)、已开格(连续地面,含 0 格与 1–8 全档浮空数字)、危险格(地面上的巢穴纹)、封印印记(画在岩顶面上)、小人两态(独立占格 + 与数字同格——后者须验"数字与引导线完整可见")、图类型色板变体组(§4.3 四槽换色,至少两套)、数字呈现组(浮空数字+引导线:地面格/岩顶格/顶行越出棋盘三种背景各一组,含 1–8 全档)、已开格结果表达组(纯安全格 + 至少两类型物件纹样)、**遮挡组**(小人站在南邻岩体的北侧,脚底被岩体自然遮住;小人与数字同格时数字仍完整可见)、**类型微光呈现组**(至少三个类型的微光在纯岩顶格上呈现——样张为静态定格测试图,非运行时行为承诺,实际可见时机见《HUD框架》「未开格类型微光」节;含两两相邻格微光并列;微光格内零角嵌/箭头/加号类附加图形)。验收:目检〈样张图含清单全部组项〉
**样张内容清单**(一图齐备,缺项即无效):未开格(合并岩体:岩顶大板+南/东崖面,含至少两种装饰变体)、已开格(连续地面,含 0 格与 1–8 全档浮空数字)、危险格(地面上的巢穴纹)、封印印记(画在已揭示地面+受光棱封印石上)、小人两态(独立占格 + 与数字同格——后者须验"数字与引导线完整可见")、图类型色板变体组(§4.3 四槽换色,至少两套)、数字呈现组(浮空数字+引导线:地面格/岩顶格/顶行越出棋盘三种背景各一组,含 1–8 全档)、已开格结果表达组(纯安全格 + 至少两类型物件纹样)、**遮挡组**(小人站在南邻岩体的北侧,脚底被岩体自然遮住;小人与数字同格时数字仍完整可见)、**类型微光呈现组**(至少三个类型的微光在纯岩顶格上呈现——样张为静态定格测试图,非运行时行为承诺,实际可见时机见《HUD框架》「未开格类型微光」节;含两两相邻格微光并列;微光格内零角嵌/箭头/加号类附加图形)。验收:目检〈样张图含清单全部组项〉
**验收项按序判定,第一项不过 = 整张否决(数字可读性定风格,风格服从可读)**:
@@ -61,7 +61,7 @@
**战斗一屏样张**:整屏样张闸已过——整屏四项机检全绿,方向稿目检已通过(用户实机目检核销,沿革查 git 提交史)
**样张现状**:格子样张已冻结,冻结凭据 = §4.1/§8.2 定值表;样张 v3 产物原件已丢失、未入 git。现行冻结态:色板与帧数档维持冻结;数字形制 = 浮空描边字(暖白字芯 + 2px 深墨描边 + 双色引导线,值见 §4.1/§3.2);色牌退役;新增图类型/变异色板须重跑本节测试。验收:机检〈冻结项定值均以 §4.1/§8.2 对应 owner 表为准〉
**样张现状**:格子样张已冻结,冻结凭据 = §4.1/§8.2 定值表;样张 v3 产物原件已丢失、未入 git。现行冻结态:色板与帧数档维持冻结;数字形制 = 浮空描边字(暖白字芯 + 2px 深墨描边 + 双色引导线,值见 §4.1/§3.2);普通数字色牌退役(封印格身份字形色牌保留,见 §3.2 封印印记条);新增图类型/变异色板须重跑本节测试。验收:机检〈冻结项定值均以 §4.1/§8.2 对应 owner 表为准〉
---
@@ -162,9 +162,9 @@
- **雷图棋盘呈现基准**:战棋地块质感 × 正俯视角——取 ITB 参考图(`references/itb_1.jpg`)的地表质感骨架:地块纹理多样、起伏体块感、岛缘/砖缘厚度;但相机一律正上方垂直向下,格线屏轴对齐,**禁等距/斜视角与菱形格**;形状语言仍守总纲圆角方形。**立体表现取抬升合并岩体+南/东崖面**(2026-11-07 用户裁,采 Link's Awakening 原版/DX 2D 的台面抬升与崖面观感,2019 重制 3D 立体模型版不作参考);砖缘厚度与岛缘立体面属形体塑造(岩顶/地面仍守 §4.1 参数域)。装饰小立牌属 ITB 参考的观感元素,本作不采用;装饰变体走氛围层纪律(纹理级/物件级形状家族,见下方装饰变体条),零信息纪律以原则三为准。验收:目检〈棋盘正俯视、格线屏轴对齐、无菱形格;未开格呈抬升合并岩体+南/东崖面〉 ←原二;原三;§2
- **地形**:一切环境体由同一圆角方形衍生:未开 = 抬升岩体(完整),已开 = 连续地面(挖出空间),雷 = 地面上的巢穴纹(危险格玩家说法见《排雷玩法》);只以“完整/挖开”叙事,不为状态新造形。验收:目检〈三格态仅以完整/挖开与巢穴纹区分,无新造状态形〉 ←原三;P4
- **装饰变体(未开格氛围层)**:抬升岩体的顶面之上叠加裂纹/碎石斑/物件级点缀等装饰。物件级装饰(小石块/陶片/骨殖等)须走**专属装饰形状家族**(与信息元素形状语言隔离、灰度下不与任何逻辑元素同形),数量配额按图类型定(候选 3–5 种,样张已冻结,§1.4),一律残缺、内凹、低对比而自然后退(§3.4);零信息纪律(分布随机、与雷/安/类型零相关、灰度下禁止制造新分类特征、分布每板一次跨轮不复掷)以原则三为准,此处不赘。验收:目检〈物件级装饰不与任何逻辑元素同形;装饰残缺低对比后退〉 ←原三
- **结果整格表达(已开格)**:已开格按当格结果整格表达(结果清单见原则三);画法——产资源格的家族物件纹样用 §3.1 道具点剪影语言(直线几何体)角嵌于格面边缘,格心留白(数字已迁浮空层,格面纹样不再需要给字形让位,但仍禁止升到浮空层);危险格巢穴纹较已开格更凶一档,形状+颜色双通道。验收:目检〈产资源格嵌家族纹样且格心留白;巢穴纹更凶一档〉 ←原三;P3
- **结果整格表达(已开格)**:已开格按当格结果整格表达(结果清单见原则三);画法——产资源格的家族物件纹样用 §3.1 道具点剪影语言(直线几何体)角嵌于格面边缘,格心留白(数字已迁浮空层,格面纹样不再需要给字形让位;封印格身份字形走色牌例外,见封印印记条,仍禁止升到浮空层);危险格巢穴纹较已开格更凶一档,形状+颜色双通道。验收:目检〈产资源格嵌家族纹样且格心留白;巢穴纹更凶一档〉 ←原三;P3
- **未开格预读通道(微光单通道)**:类型预读 = 类型微光单通道(色相+明度双码,色带 §4.2);色弱类型主通道 = 微光明度梯度(组粒度——组间互辨;§2 微光明度例外);格内零角嵌/箭头/加号类附加形(微光格只留微光)。微光画在抬升后的岩顶面内,锚点随岩顶偏移(−ESHIFT/−LIFT),禁止越出本格岩顶。验收:目检〈微光格内零角嵌/箭头/加号类附加形;微光不越本格岩顶〉 ←原一;原三
- **封印印记**:封印动作的叙事呈现 = **封印**(玩家可见专名,术语定义见 [术语表](../gdd/术语表.md)「封印」条;动作面见《排雷玩法》)——封印动作落在格上的印记称封印印记。**形 = 法阵**:一圈外环+环内一个十字,十字四端顶到环内沿、与环连成一体——两层构成取法阵通用骨架(外环=圈定范围,环内图形=力量图形,非指特定作品):环=封印圈定,十字=钉住此格。**画法 = 参数化绘制**(着色器按格边实时画环与十字,不出贴图):格边 26–46px 连续可变,任何贴图源都得非整数缩放(32px 源在 28 格缩到 0.875、在 46 格放到 1.44),像素必宽窄不均;参数化按比例画,任意格边清晰。**环外光圈**:印记外面一圈**压暗光**(纯黑只加透明度)——自环外缘起、向外衰减到格边为止、**不外溢邻格**,作用 =「此格被罩住」;禁中心亮、禁色相、禁呼吸。**尺寸定值**见 §8.2「封印印记参数表」(本处不复述数值)。**色** = 封印黑 `#000000`(§4.1),光圈只改 alpha、不新增色。**占位**:印记居**岩顶中心**(格心 + (−ESHIFT, −LIFT),岩顶随整块岩体抬升;见 §8.2),环外留 9% 格宽给光圈;封印印记只出现在未开格,不与数字同格。格内栈位见 §1.2;层序纪律——印记随所属格件脚底行:北侧(脚底≤小人)低于小人,南侧(脚底>小人)随岩体在前;迷雾态(区外全黑/记忆态)隐藏(§6.5「封印随格隐没」)。封印特效两件:常显形态即本件,施印瞬间未实装。战末金蜡封徽记仍取贴图 `seal_mark.png`(与棋盘侧参数化绘制并存,两处互不依赖)。验收:目检〈环径占格宽 82%±1px;光环居岩顶中心、只在环外且不出格;纯黑无色相〉 ←原一;P2
- **封印印记**:封印动作的叙事呈现 = **封印**(玩家可见专名,术语定义见 [术语表](../gdd/术语表.md)「封印」条;动作面见《排雷玩法》)——封印动作落在格上的印记称封印印记。**形 = 法阵**:一圈外环+环内一个十字,十字四端顶到环内沿、与环连成一体——两层构成取法阵通用骨架(外环=圈定范围,环内图形=力量图形,非指特定作品):环=封印圈定,十字=钉住此格。**画法 = 参数化绘制**(着色器按格边实时画环与十字,不出贴图):格边 26–46px 连续可变,任何贴图源都得非整数缩放(32px 源在 28 格缩到 0.875、在 46 格放到 1.44),像素必宽窄不均;参数化按比例画,任意格边清晰。**环外光圈**:印记外面一圈**压暗光**(纯黑只加透明度)——自环外缘起、向外衰减到格边为止、**不外溢邻格**,作用 =「此格被罩住」;禁中心亮、禁色相、禁呼吸。**尺寸定值**见 §8.2「封印印记参数表」(本处不复述数值)。**色** = 封印黑 `#000000`(§4.1),光圈只改 alpha、不新增色。**占位**:印记居**格心**(已揭示连续地面;环外留 9% 格宽给光圈,见 §8.2);封印印记出现在**已揭示格**上(封印受理即揭示——机制口径见《排雷玩法》动作合法性节,呈现规格与预览见《ux/battle》「封印格呈现预览」节)——格下身份字形(危/数字)按格内栈位(§1.2 数字 > 印记)垫色牌叠于法阵之上,印记与数字同格合法;该格字形不参与浮空数字层。**承载面**:封印格地面铺本关受光棱色封印石(dungeon `#C9AF83`/underdark `#A1B1BF`/deep_underdark `#918E8B`,§4.1)——新连续地面 `#695038` 系对封印黑仅 2.35–2.98:1,不达 §4.1 ≥4.5:1;受光棱系 6.45–9.94:1,零新增色。格内栈位见 §1.2;层序纪律——印记与身份色牌固定低于小人(不参与南侧格件前推),且迷雾态(区外全黑/记忆态)隐藏(§6.5「封印随格隐没」)。封印特效两件:常显形态即本件,施印瞬间未实装。战末金蜡封徽记仍取贴图 `seal_mark.png`(与棋盘侧参数化绘制并存,两处互不依赖)。验收:目检〈环径占格宽 82%±1px;光环居格心、只在环外且不出格;纯黑无色相〉 ←原一;P2
- **可挖域接触带微光(交互反馈层)**:挖掘期,英雄格周界与可挖域(概念见《排雷玩法》动作合法性节;呈现义务见《HUD框架》可挖域指示)的接触面画呼吸微光——正交邻墙 = 该侧整条边,斜向邻墙 = 共享角点 + 向两侧各延伸半条边;光只画英雄格(空间/地面)一侧、不进岩体(岩体抬升不改变接触带的逻辑格边;光画在地面侧,不贴岩顶;与装饰变体零冲突);微光 = 空间的灯光边界(画法规则)。色相 = 蓝族浅一档(§4.2 蓝条;与主角蓝框的分层由样张测试定,§1.4);呼吸 = §2.1 挖掘期唯一在动元素,减动效停格。角点为斜墙与正交墙共享、不逐格归因,逐格指认 = 悬停金框(呈现交互归《HUD框架》悬停指示域)。验收:目检〈微光只画英雄格一侧接触面:正交邻墙整条边、斜向邻墙角点 + 半边;光不进岩体〉 ←§2.1;§4.2;P1
- **岩体**:圆角岩体堆叠/合并,禁尖刺装饰——尖刺专属危险语义,装饰禁止伪装逻辑差异。验收:目检〈岩体无尖刺装饰〉 ←原三
- **棋盘地形体系|术语与定义**:未开格 = **抬升岩体**——连通的未开格(四邻:上下左右;斜角不连)合并为一块岩体,共享一块岩顶与连续崖面;已开格 = **连续地面**。素材语义名——岩顶材质 = **大岩板**,南/东崖面材质 = **崖面**,已开地面 = **地面**;**不做逐格自含瓦片**,Tiny Dungeon 拼块套与逐格 16+5 墙/地瓦片套均退役(2026-11-07 用户裁)。验收:目检〈连通未开格共享一块岩顶与连续崖面,无逐格拼缝〉 ←原三
@@ -172,10 +172,10 @@
- **棋盘地形体系|受光与崖面**:光照 = **北/西受光**(2026-11-07 用户裁)——北/西临空缘 = 2px 主亮棱 + 2px 次亮棱(暖白族就地提亮),再外 2px 深墨临空边线;南/东临空缘 = 2px 深墨内缘 + 3 段 ×2px 顶面内阴影(自缘内 0.217×格边起,黑 α≈0.27/0.16/0.08)。南崖面 = 高 LIFT、宽 = 格边 + ESHIFT,顶缘 2px 口沿高光取本关受光棱(§4.1,dungeon `#C9AF83`)+1px 深墨线,面身横向层理叠石(大岩板同族、背光暗面),禁竖条纹栅栏;东崖面 = 宽 ESHIFT、高随岩顶南缘,横向层理、外缘 1px 深墨。验收:目检〈北/西亮棱、南/东内阴影与崖面层理;南崖不越下一行、东崖不越下一列;无竖条纹〉
- **棋盘地形体系|材质与装饰**:岩顶 = **大岩板**(低频率斑驳 + 稀疏大板块缝 + 板缘浮雕倒角,单块材质覆盖整块岩体;禁逐格小砖纹与逐格边框);地面 = 连续暖褐土(低对比石屑、碎石斑;无缝,禁逐格砖缝);装饰变体 = 岩顶材质内的低对比裂纹/麻点/碎石贴花,走原则三零信息纪律,不新增逐格资产。验收:目检〈岩顶为单块大岩板、无逐格拼缝;地面连续无缝;装饰不构成逻辑线索〉
- **棋盘地形体系|投影与整板悬崖**:南/东临空岩体在地面投 4 段 ×4px 渐隐影(黑 α 0.44/0.30/0.16/0.07,自崖面外缘起);整板悬崖 = 板底/右侧 `DEPTH_PX 8px` 暗面 + 岩层横纹 + 口沿高光 + 接触 AO + 底线收边 + 悬浮影,色取棋盘环境子板(§4.1)。验收:目检〈南/东投影四段渐隐;整板悬崖结构件齐备〉
- **棋盘地形体系|绘制序与遮挡**:层序 = 地面 → 投影 → 数字所属格的**地面阴影圆**(开启格地面,位于小人之下,小人站该格时遮住圆)→ **按脚底 y 排序的深度对象**(岩体 = 其最南格格底;小人 = 所在格格底;升序绘制,南侧岩体自然遮住小人脚底)→ 顶面贴花(封印印记/类型微光/变异纹带,随岩顶锚点偏移 −ESHIFT/−LIFT;只在未开格岩顶)与交互反馈(悬停/路径/可挖域微光)随所属格件同序——北侧(脚底≤小人)低于小人,南侧(脚底>小人)随岩体在前 → **浮空数字+引导线(最顶)**。验收:机检〈深度对象按脚底升序绘制;格件贴花/交互随脚底行分档;浮空层 z 最高〉+目检〈南侧岩体遮住小人脚底;顶行数字越出棋盘仍完整〉
- **棋盘地形体系|浮空数字(信息层)**:数字与格面字形(危/?)脱离格面,浮在格上方——字高 21px(7px 像素字 ×3,固定不随格边缩放)、2px 深墨描边、字芯暖白 `#F0E2C8`、1px 浮空投影;字底距格顶 15px;引导线 = 2px 双色实线(左深 `#1A140E`/右浅 `#E8D3AB`),自字底 +3 落到格顶 +4;格顶 +7 处画 10×6 四层柔边小阴影圆(标明数字所属格),圆画在开启格地面、位于小人之下,不盖小人。浮空层 z 最高,任何地形/小人/特效不遮数字与引导线。顶行数字出棋盘上缘,D 区上方预留 44px(字高 21+间距 15+描边 2+余量 6)。验收:机检〈字高 21、间距 15、顶预留 44;浮空层 z 最高〉+目检〈顶行数字与引导线完整;阴影圆指向正确格〉
- **棋盘地形体系|绘制序与遮挡**:层序 = 地面(封印格地面铺本关受光棱色封印石)→ 投影 → 数字所属格的**地面阴影圆**(开启格地面,位于小人之下,小人站该格时遮住圆)→ **按脚底 y 排序的深度对象**(岩体 = 其最南格格底;小人 = 所在格格底;升序绘制,南侧岩体自然遮住小人脚底)→ 顶面贴花(类型微光/变异纹带,随岩顶锚点偏移 −ESHIFT/−LIFT;只在未开格岩顶)与交互反馈(悬停/路径/可挖域微光)随所属格件同序——北侧(脚底≤小人)低于小人,南侧(脚底>小人)随岩体在前;封印印记与封印格身份色牌在已揭示地面、固定低于小人(不参与南侧前推)→ **浮空数字+引导线(最顶)**。验收:机检〈深度对象按脚底升序绘制;格件贴花/交互随脚底行分档;浮空层 z 最高〉+目检〈南侧岩体遮住小人脚底;顶行数字越出棋盘仍完整〉
- **棋盘地形体系|浮空数字(信息层)**:数字与格面字形(危/?)脱离格面,浮在格上方——**封印格除外**,其身份字形按封印印记条垫色牌叠于法阵之上、不进浮空层;字高 21px(7px 像素字 ×3,固定不随格边缩放)、2px 深墨描边、字芯暖白 `#F0E2C8`、1px 浮空投影;字底距格顶 15px;引导线 = 2px 双色实线(左深 `#1A140E`/右浅 `#E8D3AB`),自字底 +3 落到格顶 +4;格顶 +7 处画 10×6 四层柔边小阴影圆(标明数字所属格),圆画在开启格地面、位于小人之下,不盖小人。浮空层 z 最高,任何地形/小人/特效不遮数字与引导线。顶行数字出棋盘上缘,D 区上方预留 44px(字高 21+间距 15+描边 2+余量 6)。验收:机检〈字高 21、间距 15、顶预留 44;浮空层 z 最高;封印格无浮空 entries〉+目检〈顶行数字与引导线完整;阴影圆指向正确格〉
- **棋盘地形体系|旧资产处理**:旧 `terrain_*` 63 件(16 墙瓦片 + 5 地面变体,三关各一套)全部退役——无参考价值者直接删除,不归档;`seal_mark.png` 保留(战末金蜡封专用)。验收:机检〈旧 terrain_* 零引用、零在库;seal_mark 在库〉
- **变异格形状通道**(双通道之"形",与色无关):岩体外形由合并岩体决定、不因变异改变;变异纹带画在本格抬升岩顶面内缘(锚点随岩顶偏移 −ESHIFT/−LIFT),一圈定宽专属几何纹带(纹带形全作不与他类共用),格心留白予封印印记(数字已迁浮空层);验收标准 = **灰度截图下仍可分类**。种类清单 = 地形变异六类型已定稿(地形变异 GDD;荆棘丛/藤蔓缠绕/岩浆/烬覆/落石/矿尘):**纹带形按轴只分两种**——伤害轴(荆棘丛/岩浆/落石)= 刺/缺排斥形,信息轴(藤蔓缠绕/烬覆/矿尘)= 遮蔽/缠绕形;同轴跨图类型只做色板变体(§4.3 `variant_tint` 槽),不做新纹形。岩浆纹带橙红须与危险红互辨(变异格不是雷,色相邻近有混淆风险);信息轴遮蔽呈现禁止侵入浮空数字与引导线占位区(§1.4 验收 1 延伸)。验收:目检〈六类型纹带灰度下仍可分类,同轴不新增纹形;岩浆纹带与危险红互辨〉 ←原一(纹理位次第 4,让位于浮空数字/小人/封印印记);原三
- **变异格形状通道**(双通道之"形",与色无关):岩体外形由合并岩体决定、不因变异改变;变异纹带画在本格抬升岩顶面内缘(锚点随岩顶偏移 −ESHIFT/−LIFT),一圈定宽专属几何纹带(纹带形全作不与他类共用),格心留白(数字已迁浮空层;封印印记在已揭示地面承载,不与变异纹带同格);验收标准 = **灰度截图下仍可分类**。种类清单 = 地形变异六类型已定稿(地形变异 GDD;荆棘丛/藤蔓缠绕/岩浆/烬覆/落石/矿尘):**纹带形按轴只分两种**——伤害轴(荆棘丛/岩浆/落石)= 刺/缺排斥形,信息轴(藤蔓缠绕/烬覆/矿尘)= 遮蔽/缠绕形;同轴跨图类型只做色板变体(§4.3 `variant_tint` 槽),不做新纹形。岩浆纹带橙红须与危险红互辨(变异格不是雷,色相邻近有混淆风险);信息轴遮蔽呈现禁止侵入浮空数字与引导线占位区(§1.4 验收 1 延伸)。验收:目检〈六类型纹带灰度下仍可分类,同轴不新增纹形;岩浆纹带与危险红互辨〉 ←原一(纹理位次第 4,让位于浮空数字/小人/封印印记);原三
### 3.3 UI 形状语法(方向指导,细则见 §7.1)
@@ -208,11 +208,11 @@
- **棋盘数字呈现 = 浮空描边字(全板统一,2026-11-07 定)**:字芯暖白 `#F0E2C8` + 2px 深墨描边 `#1A140E` + 1px 浮空投影;引导线 2px 双色(左深 `#1A140E`/右浅 `#E8D3AB`);不使用数字底牌、不随图类型换色、不再有亮直/深直/落牌三态。对比判据 = `max(字芯对底, 描边对底) ≥4.5:1` 且 `字芯对描边 ≥4.5:1`(最亮底岩顶 `#9C8462` 实测描边对底 5.1:1);机检面 = 浮空字几何/对比测试,目视验证面 = board_shot_probe 渲染帧。验收:机检〈四类底复合对比 ≥4.5;字高 21/间距 15/顶部预留 44 定值〉
- **UI 数据墨 = 深墨族 `#33261B` 系**(L≤0.027,§7.2);棋盘数字底牌已退役、不再使用。验收:机检〈UI 数据墨取深墨族,L≤0.027〉
- **八色弃用**:八色必与红/金/蓝/紫赤语义冲突,故弃。验收:方向性
- **封印黑 `#000000`**|封印宣告色,无色相:玩家的宣告"此格封印"。封印现行 = 无色相黑——紫色与微光蓝族(魔法微光 256° 等)色相冲突,黑色无色相天然避开 300°–345° 禁入带与全部微光色带;未开格墙顶明度 ≈113 对纯黑对比 ≥4.4:1,纯黑实心剪影单靠明度差即成立,不依赖任何浅色细件。封印紫不再用于棋盘(色史归 git 提交史);红族棋盘侧只剩危险红;主角随身背包件为朱红矩形件(人设侧见 character-design),与棋盘封印色脱钩。验收:机检〈封印宣告色 = #000000,未开格墙顶对其对比 ≥4.4:1〉 ←原一;P2
- **封印黑 `#000000`**|封印宣告色,无色相:玩家的宣告"此格封印"。封印现行 = 无色相黑——紫色与微光蓝族(魔法微光 256° 等)色相冲突,黑色无色相天然避开 300°–345° 禁入带与全部微光色带;承载面 = 本关受光棱色封印石(dungeon `#C9AF83`/underdark `#A1B1BF`/deep_underdark `#918E8B`,见 §3.2 封印印记条)对纯黑对比 9.94/9.56/6.45:1,纯黑实心剪影单靠明度差即成立,不依赖任何浅色细件;新连续地面 `#695038` 系对纯黑仅 2.35–2.98:1,故不作封印承载面。旧裸土 `#B39A78` 随旧地板瓦片退役。封印紫不再用于棋盘(色史归 git 提交史);红族棋盘侧只剩危险红;主角随身背包件为朱红矩形件(人设侧见 character-design),与棋盘封印色脱钩。验收:机检〈封印宣告色 = #000000,受光棱封印石对其对比 ≥4.5:1〉 ←原一;P2
- **危险红 `#A8321F`**|危险色:雷 = 被惊动的巢穴,兑现档红,更深更沉。验收:机检〈危险语义色 = #A8321F〉 ←原三;P2
- **奖励金 `#F0A828`**|已兑现的收益:GP/宝箱/蜡封,全作唯一亮金。验收:机检〈收益语义色 = #F0A828〉 ←P2;§2.3/2.4
- **小人蓝 `#2E86DE`**|小人主色:棋盘唯一"己方",与光标准线同族。验收:机检〈己方语义色 = #2E86DE〉 ←P1;§2.1
- **数字色牌 `#F5EAD3`**|**棋盘侧退役**(2026-11-07 浮空数字定案;棋盘不再使用数字底牌)。token 若被 UI 其他面复用按 UI 侧登记,不属棋盘数字呈现;棋盘绘制路径零色牌调用。验收:机检〈棋盘绘制路径零色牌调用〉 ←原二
- **数字色牌 `#F5EAD3`**|**普通数字棋盘侧退役**(2026-11-07 浮空数字定案;普通数字不再使用底牌);**封印格身份字形色牌保留**(`UiKit.DIGIT_PLATE`+`DIGIT_PLATE_EDGE`,叠于法阵之上,见 §3.2 封印印记条,2026-11-07 封印改版定案)。token 若被 UI 其他面复用按 UI 侧登记,不属棋盘数字呈现;棋盘普通数字绘制路径零色牌调用,封印格色牌为唯一例外。验收:机检〈普通数字绘制路径零色牌调用;封印格色牌在位〉 ←原二
- **收益金亮档 `#E0B860`(GOLD_BRIGHT)**|UI 徽标可读性亮档 token:敌顺位徽标①②③与重甲徽标对舞台底 6.47:1(GOLD_DEEP 3.26:1 不达 §7.2 带,故另立亮档);金族亮档,与奖励金 #F0A828 同族不同岗——奖励金 = 已兑现收益语义(§4.2 金条),本 token 只承载徽标可读性、不占收益语义。GOLD_DEEP/HERO_BLUE 常量本尊不动(standee 意图判据等语义岗现行使用)。验收:机检〈GOLD_BRIGHT 对舞台底 ≥4.5:1〉
- **小人蓝亮档 `#7EBAF0`(HERO_BLUE_BRIGHT)**|魔法护甲徽标对舞台底 5.88:1(HERO_BLUE 3.23:1 不达带,故另立亮档);蓝族亮档,与小人蓝 #2E86DE 同族不同岗——本 token 只承载徽标可读性,不占用「蓝只说己方」的语义。验收:机检〈HERO_BLUE_BRIGHT 对舞台底 ≥4.5:1〉
- **图标槽柔和档 `#806A4E`(SLOT_BACKDROP_SOFT)**|BCard 图标槽底:对米色卡面 PANEL_DATA 3.69:1(深档 SLOT_BACKDROP #3E362C 8.53:1 过强、像硬贴补丁,故槽底另立柔和档),槽内白图标 5.14:1,满足 ≥3:1 图形参考线硬约束;仅 BCard 图标槽换档,B 区状态槽仍用深档 SLOT_BACKDROP。验收:机检〈槽内白图标对槽底 ≥3:1〉
@@ -224,7 +224,7 @@
- **换色合并登记五处(用户裁,部件色落值)**|按本表「不过门即触发换色合并、禁加色绕行」执行,五处**零新增额度**(各件原拟新增候选一律不取):① 主角铲刃浅铁灰(原拟 `#A8AEB8`)→ 并入 `FOG_MEMORY_INK #C4C4C4`,**部件取值 = `#C4C4C4`**;② 主角提灯铜色(原拟 `#C08A3A`)→ 并入 `GOLD_DEEP #B07A14`,**部件取值 = `#B07A14`**;③ 食人魔酋长暖褐皮(原拟 `#B08050` / `#A97C4E`)→ 并入 `暖石 #B8A582`,**部件取值 = `#B8A582`**;④ 石君王灰皮(原拟 `#9A968C` / `#949089`,按**次级皮**口径——板甲视同体块主色、灰皮属次级皮故不受 L 0.22–0.45 约束)→ 并入 `STONE #786E62`,**部件取值 = `#786E62`**;⑤ 轻甲护甲图标木色 `#8A5A34`(深素底下 2.72:1 不过门;原拟新增木色亮档 `#C49A5E`)→ 并入 `WARM_FAINT #CDB88C`,**部件取值 = `#CDB88C`**(8.08:1)。**一色两岗代价已随裁定接受**:① 的 `#C4C4C4` 语义岗 = 迷雾记忆墨(§6.5)、② 的 `#B07A14` 属金族(§4.2 金只说收益与兑现)——两者均作角色随身件材质色用,不进信息层与语义物件岗;③④⑤ 无语义岗冲突。验收:机检〈五处部件取值点全部改指合并目标色,四枚原拟新增候选值零入板〉
- **换色合并登记三处**|按本表「不过门即触发换色合并、禁加色绕行」执行,三处零新增额度:①地台顶缘受光线 `#C4AC84` 已并入 `#C9AF83`(掩尘枚 `BRICK`,与 §8.2 高墙口沿高光同枚)——源常量已删除(全仓零引用,退役断言测试串为预期例外),绘制点全部改指 `#C9AF83`;②地台描边 `#5A4C3A` 已并入 `#4C4038`(代码 `PLAZA_EDGE`,ui_kit 唯一落点)——源常量已删除,绘制点全部改指;③舞台底 `#3E342A`(代码 `STAGE_BG`)→ 并入 `#3E362C`(状态槽底)——源常量已删除(常量表零键),背景资产接管该绘制面,程序化平色缺图回退改指 `SLOT_BACKDROP`(`#3E362C`)。程序化回退绘制与已落合并值的贴图保持一致。验收:机检〈①②两枚源常量全仓零引用,绘制点全部改指合并目标色〉
**对比硬指标(可测)**:数字可读性由**浮空描边字**承载——棋盘数字字芯暖白 `#F0E2C8` + 2px 深墨描边 `#1A140E`,判据 = `max(字芯对底, 描边对底) ≥4.5:1` 且 `字芯对描边 ≥4.5:1`;抽测底 = 地面/岩顶/崖面/板外暗底四类(最亮底岩顶实测描边对底 5.1:1);不使用数字底牌、无色牌后备。岩顶/地面/崖面底槽(含全部图类型)与类型微光之间存在**相对亮度与相对饱和关系**——禁止底槽吞没微光环、禁止岩顶与地面同档;本节只定这层关系,阈值与准入判定由图类型色板互辨抽测的执行件承载(用法登记见 [art-pipeline-tools](art-pipeline-tools.md))。已开格地面走世界底层档(见上)。物件识别不依赖底色(§5.1 轮廓通道),封印印记、金色不作识别依赖。验收:机检〈四类底复合对比 ≥4.5;岩顶/地面底对微光环与对彼此的相对亮度关系经互辨抽测准入〉 ←原二;地形变异 AC-9
**对比硬指标(可测)**:数字可读性由**浮空描边字**承载——棋盘数字字芯暖白 `#F0E2C8` + 2px 深墨描边 `#1A140E`,判据 = `max(字芯对底, 描边对底) ≥4.5:1` 且 `字芯对描边 ≥4.5:1`;抽测底 = 地面/岩顶/崖面/板外暗底四类(最亮底岩顶实测描边对底 5.1:1);普通数字不使用数字底牌、无色牌后备(封印格身份字形色牌为唯一例外,见 §3.2 封印印记条)。岩顶/地面/崖面底槽(含全部图类型)与类型微光之间存在**相对亮度与相对饱和关系**——禁止底槽吞没微光环、禁止岩顶与地面同档;本节只定这层关系,阈值与准入判定由图类型色板互辨抽测的执行件承载(用法登记见 [art-pipeline-tools](art-pipeline-tools.md))。已开格地面走世界底层档(见上)。物件识别不依赖底色(§5.1 轮廓通道),封印印记、金色不作识别依赖。验收:机检〈四类底复合对比 ≥4.5;岩顶/地面底对微光环与对彼此的相对亮度关系经互辨抽测准入〉 ←原二;地形变异 AC-9
**色板增量纪律(数值状态 = 待定现用)**:全作色板硬上限 **48 色**(防色板无限膨胀的封口条款;**额度已用尽、余量 0**,后续余量以色板检查器实报为准);新色入板须同时过两道门:①§4.2 语义检查(不占语义色段/禁入带);②**与既有任一色的 RGB 三通道差禁止全部 <8/255**——即三通道里至少一通道 ≥8/255 即过门,「三通道差全部 <8」才算不过门(条文原写的「三通道差 ≥8/255」有「逐通道全过」与「禁止全灭」两种互斥读法,前者下现行色板 247 对不达标、与本节自认的「现行即执法」合规状态不能同真,故取后者;用户裁)。**额度已用尽:后续任何新增须先压缩或合并既有色**——按本节「额度不足时呈报作者」口径呈报,**压缩或合并哪几枚由作者裁,本节不代裁、不预指对象**。反例实证:#3A2E22 ↔ #362B20 三通道差均 <5,肉眼不可辨,同板共存只增噪。不过门即触发「换色合并」(把意图色并进最近既有色),**禁加色绕行**;加色义务同时登记于本表修订。**「待定现用」态释义**:数值**现行即执法**(色板检查器按此拦,§8.3)——非候选(不存在"等冻结"空窗)、非死规(后续需求引入时可变);调整走本节修订并登记,改值 = 修订不是违约。验收:机检〈色板总数 ≤48;新色入板过两道门〉
@@ -401,13 +401,14 @@
- **情绪与色域**:承载 §2.8——全作最暖:烛光黄 `#F6C667` × 木色 `#8A5A34`(§7.2),中等饱和,无棋盘式高对比。
- **场景构成**:一张可点击的营地场景图(1280×800,与工程档同幅面)。远景=天空与远山;中景=**独立成层的木围墙**(密排竖木桩、桩顶削尖、不透缝无横木)与**半开的营地大门**;近景=中央篝火、左侧锻炉铁砧、右侧帐篷与宝箱、帐篷旁的笔记本,另有木桩与挂在围墙上的布告板。**中景带自底部往上约 2/3 处起**;**近景物件可覆盖围墙,唯一例外=中间门洞必须留空、不被任何物件遮挡**。七个可点物件的功能绑定归 `camp.md`,本节只定画法。
- **图层拆分(10 张)**:场景按「远景/中景/近景要素各自成层」拆——`scene_camp_bg` 背景层(z0,不透明全幅)=天空+远山+整片地面+门洞内一条通向远处的小路;`scene_camp_wall` 围墙层(z1,透明底)=横贯全宽密排木桩墙(左右两段)+两根略高的门柱,**门洞处留透明缺口**透出背景层的小路;`scene_camp_board` 布告板(z2)、`scene_camp_gate` 门扇(z3)、`scene_camp_dummy` 木桩/`scene_camp_forge` 锻炉/`scene_camp_tent` 帐篷(z4)、`scene_camp_chest` 宝箱/`scene_camp_notebook` 笔记本(z5)、`scene_camp_campfire` 篝火(z6,最前)=八个物件层,各透明底单件一张。**帐篷**为承载件、不可点。围墙独立成层的好处:桩高可单独锁死、门洞做透明缺口、背景层不混入暖木色。提示词与坐标 = `camp-scene-prompts.md`,消费常量 = `asset-path-map`。
- **交付形态**:**背景层 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)〕)。
- **交付形态**:**背景层 1 张(不透明全幅)+ 围墙层 1 张 + 八个物件层 = 10 张**,其中 9 张透明底、1 张不透明全幅 + 一份坐标表(贴图矩形/命中矩形/z 序)。背景层由**远景片 `bg_far`(2560×734)与地面片 `bg_floor`(2560×866)两片出图、按 y734 拼成一张**——整张一次出图时模型把远山挤到画面下半部,被围墙挡住后完全消失(实测三张里两张山体上缘落在墙顶以下)。接缝整条落在围墙背后,画面无接缝。**篝火例外**:命中矩形只取下半(火堆),上半(火焰)只作特效展示。围墙层落盘按细条裁,不做全幅(全幅约 3.9 MB 显存,细条约 0.35 MB);**实装落盘矩形 `(0, 220, 1280, 200)`、底锚 y420**(2026-11-07 用户裁:墙高 200、顶 y220;素材 1280×164 按 200 高显示拉伸,桩身/门柱比例随缩放;围墙带 (0,220,1280,200))。
- **墙脚入土线(2026-11-07 用户裁)**:围墙不与地面以直线相接——墙底按 1–7px 低频起伏裁成透明缺口,透出背景层地面纹理,读作桩子插入土中。像素级判据:墙底不存在贯通全宽的直线边界,缺口透出的像素与背景层同列地面纹理同源。参考图指针=[camp-scene-prompts](camp-scene-prompts.md) §5 第 12 条。验收:目检〈墙底无贯通直线、桩脚有入土感〉
- **色彩与氛围依据**:营地的暖由晒白的地面、木色物件与烛光黄承载,安全温馨,与冒险中的地下城形成对比;远景=正午强日照——天空淡晴蓝(高明度低饱和的清蓝)、远山一整片连续的淡蓝灰山体(禁层叠轮廓带与阴影台阶)、草地与土路晒白,整体淡而退后,近景暖调为主。现行口径 = 本文件 §2.8「全作最暖」(管菜单容器与元进度,不约束背景天空)与本节「情绪与色域」条。本节不复述色值;新增环境色按 §4.1 场景环境子板条款登记(背景层自板 ≤32 色、营地屏双板制并集 ≤64,过 §4.2 语义检查与 RGB 三通道差 ≥8/255 两道门)。
- **背景层环境色与自板(2026-11-07 录入登记)**:背景层单张走自板 ≤32 色,环境色五族=淡晴蓝天空、淡蓝灰一整片远山、晒白浅黄绿草地、暖米白土路、泛白米黄营地地面;新增色过 §4.1 两道门,近色走换色合并(本次 4 对:九件板 `#D7B281`→`#D1B27F`;全局色板基线 `#B3C8D3`→`#BAC1D2`、`#DCEDF5`→`#D8ECF0`、`#E7E5D2`→`#EBE8CE`)。像素级判据:机检〈单张像素域唯一色 ≤32;palette_check 对九件主色板基线与全局色板基线(`game/tests/golden/palette_baseline/`)均零近色红〉;目检判读句=[camp-scene-prompts](camp-scene-prompts.md) §4 远景片两条(淡晴蓝/一整片山体)。参考图指针=[asset-path-map](asset-path-map.md) §3 scene/ 行(`scene_camp_bg`)。验收:机检〈单张 ≤32 且 palette_check 零红〉+目检〈天空淡蓝不深饱和、山体一整片无层叠带〉
- **交互四态(零新素材,全部程序化)**:悬停=**轮廓亮边 + 浮动**(浮动须整像素吸附);按下=位移 1px;**禁用/未解锁=降明度 + 去饱和**(§2 已缩窄至情绪域,状态表达不受限),**仅锻炉(锻造工坊)与宝箱(秘宝匣)两件会有此态**;已解锁=常态。
- **钥石余额**:常驻画面顶部的一行小 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 色、营地屏双板制并集 ≤64,过 §4.2 语义检查与 RGB 三通道差 ≥8/255 两道门);② **门扇与门洞的高宽比例**——门扇贴图 240×138(锁定 240×147),随门洞 y283–367 压扁重画,坐标按门洞 x520–760 对齐;③ **帐篷的落地尺寸**——251×170 落位 (982, 335),上端遮住围墙下部。
- **已裁(2026-11-01 用户裁)**:① **营地环境子板色值**——不前置拍板:量化场景实际用色后按 §4.1 场景环境子板条款登记(背景层自板 ≤32 色、营地屏双板制并集 ≤64,过 §4.2 语义检查与 RGB 三通道差 ≥8/255 两道门);② **门扇与门洞的高宽比例**——显示矩形 240×147(贴图可见高 138),门扇件沿用 240×211 由运行时压扁(2026-11-07 用户裁「不动美术素材」),坐标按门洞 x520–760 对齐、底压墙底 y420;③ **帐篷的落地尺寸**——251×170 落位 (982, 335),上端遮住围墙下部。
### 6.3 三关图类型色板候选(board_type 换挡方向)
@@ -430,7 +431,7 @@
- **菱形灯光区(遮罩几何)**:灯光区 = 以角色所在格为中心、**曼哈顿距离 ≤ LIGHT_RADIUS** 的格集,区内照常渲染。LIGHT_RADIUS 走表值/常量参数化,**基准 8**——现行事实源「灯光区起步半径=微光环 8 格」的落码取值(半径数值沿微光「周围 8 格」的 8 起步,实测后可调)。**两圈分立注**:类型微光作用圈 = 英雄贴身 8 邻格(与可挖域同圈,现行事实源《HUD框架》「未开格类型微光」节),**不随灯光区扩**(权威文档配套裁决)——灯光区只裁可见性,微光锚仍是可挖域,两圈几何分立、半径参数互不读写。提灯插值位:FOG_LANTERN_BONUS = **+2**(flat 整数档,权威文档配套裁决;落码常量名 `board_view.gd FOG_LANTERN_BONUS`),**已接效果入库**(载体=秘宝·同调位 fog_lantern「魔法提灯」行,组合根每战装配链显式重算接线;呈现画法零改动——本节只管"怎么画",机制现行事实源仍归《HUD框架》)。边界随角色格位逐格重算,无半径插值动画。←现行事实源;§2 光照氛围允许域
- **区外未开格:全黑不渲染(不透明档,照抄现行事实源)**:现行事实源原文「区外**未开格=全黑不渲染**」——不透明纯黑(L=0),**非高遮蔽半透档**。理由注记(为何不用高遮蔽档):①三档信息阶梯干净——未开格不可见=零信息、记忆格=弱信息、活格=全信息,叠读不打架;②高遮蔽档下砖面纹理隐约残留,与「类型微光=未开格唯一信息通道」(§4.2/§4.6.3)冲突——纹理残影泄格面观感;③现行事实源验收线「区外未开格不可见」须二值态才能逐格断言。←现行事实源;§4.2
- **区外已开格:暗淡记忆态(灰显可读档)**:画法 = **地面材质原样 + 灰罩压顶**——地面直画后 `FOG_MEMORY_PLATE #3A3A3A` α0.66 灰罩盖顶,素材零改动(原「程序化素色记忆板平涂」已弃用);格内元素灰阶随罩隐现:**记忆墨 #C4C4C4(中性灰 L≈0.55)**。记忆态数字以记忆墨直画、不使用数字底牌(浮空层随格隐没)。**封印印记不在记忆态面**:印记只落在未开格,区外未开格全黑——印记随格隐没、灯光区移回复显(纯呈现,封印数据零影响,现行事实源的显式推论)。←HUD框架 可访问性基线;§4.1;§3.2
- **区外已开格:暗淡记忆态(灰显可读档)**:画法 = **地面材质原样 + 灰罩压顶**——地面直画后 `FOG_MEMORY_PLATE #3A3A3A` α0.66 灰罩盖顶,素材零改动(原「程序化素色记忆板平涂」已弃用);格内元素灰阶随罩隐现:**记忆墨 #C4C4C4(中性灰 L≈0.55)**。记忆态数字以记忆墨直画、不使用数字底牌(浮空层随格隐没)。**封印印记与封印格身份色牌不在记忆态面**:封印格已揭示,区外记忆态灰罩下印记与色牌随格隐没、灯光区移回复显(纯呈现,封印数据零影响,现行事实源的显式推论)。←HUD框架 可访问性基线;§4.1;§3.2
- **记忆态可读性判据**:数字对合成底**归一后(真实渲染面)三关 ≥4.5:1**(WCAG 硬指标;合成底取 §4.1 三关地面色叠加 `FOG_MEMORY_PLATE` α0.66;行值以实现批 `ui_contrast_probe` 实测登记,测点=该探针迷雾段)。旧瓦片均值派生随 `terrain_*` 退役,不再作为测点基准。可辨性自查(三通道,随样张/实机自检):①对全黑未开格——合成底 #3E3731(L≈0.036)对 L0,实机目检常光档可分(暗房档一并抽检);②对灯光区内活态已开格——主判据=数字墨灰度差(记忆墨 L0.55 对活态字芯 #F0E2C8 L≈0.77,灰度 ΔL≥0.20)+ 色相通道(记忆态近中性灰,对活态暖底暖字芯);③格态三形在记忆态保持灰度可辨——形通道经材质+灰罩形态不变,沿 §1.4 验收 2 同源判据。验收:机检〈ui_contrast_probe 迷雾段记忆墨对比 ≥4.5:1〉←§1.4
- **迷雾过渡边:硬边(逐格二值)**:灯光区边界 = **整格二值切换**(区内全渲染 / 区外全黑或记忆态),**不做渐变过渡带、不做半透明晕边**。理由:①边界是信息边界不是氛围晕——渐变中间态 =「看得见但看不清」,破数字可读硬指标与记忆态明度档;②现行事实源验收断言(边界格呈态、两态逐位一致)须格级二值可判;③与棋盘地形体系(§3.2)的整格表达、ITB 格内信息分层气质同轴。无动画,减动效无涉。←现行事实源;§1.2 原则一;§3.2
- **与结算期去饱和的叠加共存**:结算期去饱和偏移(settlement_fx_view,同族先例)与迷雾调制叠加规则 = **记忆态为终态档**——区外已开格在结算期保持记忆态档、**不再叠加**结算去饱和(灰显已含去饱和语义,二次调制无信息增量且明度不可控);灯光区内格照常吃结算去饱和;全黑格两态无差。叠加态随实施批共存校验 + 探针断言(provisional,实机目检可代拍)。验收:机检〈叠加态下记忆态灰显不被二次调制〉
@@ -532,7 +533,7 @@
- **画布基准(暂定值)**:
- **主角人设原图 = 48×72**(1× 基准,暂定;与站姿降采样可读性验证一致);
- **棋盘小人 = 32×32 二头身派生版(暂定,允许更小)**——独立资产,自人设定稿压缩派生(character-design §5.4),不吞数字位、占位 ≤ 0.75× 格边长;板越小占比摩擦越大(13×13 / 14×14 更小,算例见 [battle.md](../ux/battle.md)「棋盘格尺寸公式」),32px 源档的去留已定终值 = **保留 32×32 源档**(沿「独立小源档」路线;N=12 板 38px 档下显示占位上限 = 0.75×38 ≈ 28px,正落本节乙类非整数缩放许可范围,**连豁免都不用再办**),待决标注注销;
- **封印印记参数表(绘制件,非贴图件)**:形与画法见 §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;**圆心 = 岩顶中心(格心 − ESHIFT/−LIFT,ESHIFT/LIFT 见 §3.2)**。色 = 封印黑单色(光圈只改 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、圆心落岩顶中心、光圈不越格边〉
- **封印印记参数表(绘制件,非贴图件)**:形与画法见 §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;**圆心 = 格心**(已揭示地面;承载面 = 本关受光棱色封印石,见 §3.2;不再随岩顶偏移)。色 = 封印黑单色(光圈只改 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 屏,**扫雷盘不从 D 区裁尺寸**(区高事实源 = 布局表 `battle_layout.tres`;2026-11-07 顶栏搬两侧后 D 区全高归棋盘,格边按 battle.md 公式现算——基座档 12×12=38px、14×14=32px,不再受 AP 条高度挤压);② 站桩内部重排——立绘双脚落石台上缘(弃盒内居中)、**血条+状态/名字行落底排**(立绘→血条→状态 顺序,全场血条对齐一线;血条嵌石板位、台缘线留作地面线、落影保留接地感),状态空时该行退化为名字行;③ 大体档(显示 >150px 级:食人魔酋长/蛛化卓尔/灰矮人石君王)**允许向上溢出盒顶 N px**(N 按实测尺寸算定,**与背景/光柱无关**——现行溢出区恰为临时光柱氛围层〔§6.1 方向稿四柱,x 基准 110/900/1055/1210@1280,`battle_stage_view.gd:47-56`〕,正式舞台背景插画(三关变体)到位后该层直接退役(背景不画光柱),**溢出判定不随之变更**;横向禁止压到顶部中央轮次·阶段指示,溢出上限用真图复算 x 区间定),血条仍落底(落底排的第二个收益=头部探顶不带出血条);意图徽标悬浮头顶上方(探顶档压胸);④ 「血条悬浮头顶+立绘盒内居中」排式已弃——盒顶固定 y 与盒内居中在高盒下拉开血条-立绘空洞。验收:目检〈血条一线对齐、大体档溢出不压轮次指示〉
- **站桩石板地台件**:`scene_stage_plaza.png`,源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定、不吃 §4.3 `wall_ambient` tint(独立理由见 §6.1 近景地台独立件条)。验收:机检〈地台件按九宫格拉伸后顶缘受光线宽度恒为 2px〉
+13 -12
View File
@@ -1,4 +1,4 @@
# 营地场景 · 分层生图提示词卡 v1.8
# 营地场景 · 分层生图提示词卡 v1.9
> **性质**:营地场景图(1280×800,一张可点击场景图 + 十张分层:背景层 1 + 围墙层 1 + 八个物件层)的出图执行侧文件。**场景级资产,与角色级 [art-prompts](art-prompts.md) 并列,两文件互不覆盖**。
> **依据**:色彩与氛围 = [art-bible](art-bible.md) §2.8 与 §6.2(营地的暖由晒白的地面、木色与烛光件承载;远景走正午强日照、天空淡晴蓝,与冒险中的地下城形成对比;本卡不复述色值);坐标与层序 = 本卡 §2.3(运行期单一事实源 = `game/data/tables/camp_scene_layout.tres`);物件命名与消费链 = [asset-path-map](asset-path-map.md);画法与交付形态 = [art-bible](art-bible.md) §6.2。画法与用色冲突时以 art-bible 为准。
@@ -85,11 +85,11 @@ Pixel art style. True pixel art game ground texture, chunky square pixels, stric
2. 按 y367 上下拼接成 1280×800
3. 量化到背景层自板(≤32 色;营地屏双板制、屏并集 ≤64,见 art-bible §4.1 营地行)
4. 需要 2× 档时整数倍放大到 2560×1600
5. 接缝不做过渡处理——它整条落在围墙(203–367)背后
5. 接缝不做过渡处理——它整条落在围墙(220–420)背后
### 2.2 围墙层(`scene_camp_wall`,不可点,透明底)
**出图画布**:2560×1600(与背景层同画布,门洞 x 坐标直接对齐);墙横贯画在出图 y406–734 这条带内(桩身 y434–734,门柱 y406–734),其余为纯色底 `#F2F2F2`。落盘由 S4 后处理裁成终稿外接矩形 `(0, 203, 1280, 164)` / 出图 `(0, 406, 2560, 328)` 的细条,**不做全幅 1280×800 落盘**(全幅约 3.9 MB 显存,细条约 0.35 MB)。
**出图画布**:2560×1600(与背景层同画布,门洞 x 坐标直接对齐);墙横贯画在出图 y406–734 这条带内(桩身 y434–734,门柱 y406–734),其余为纯色底 `#F2F2F2`。落盘由 S4 后处理裁成素材细条 `1280×164`(出图 `(0, 406, 2560, 328)`),**不做全幅 1280×800 落盘**(全幅约 3.9 MB 显存,细条约 0.35 MB);**显示矩形 `(0, 220, 1280, 200)`(2026-11-07 裁,运行时按 200 高拉伸)**。
**画什么**:横贯全宽的密排竖木桩墙(左右两段,桩身终稿 150 高)+ 两根略高的门柱(终稿 164 高),门洞处完全透明
**不画什么**:天空、远山、地面、小路、门扇、七个物件、角色;门洞内不画任何东西,透出背景层的小路
@@ -104,14 +104,14 @@ Pixel art style. True pixel art game layer drawn with chunky square pixels, stri
| 顶栏 HUD 保留带 | y ≤ 48 | **y ≤ 96**(保持空旷) |
| 远景片 `bg_far` | y 0–367(高 367) | y 0–734(高 734,3.5:1) |
| 地面片 `bg_floor` | y 367–800(高 433) | y 734–1600(高 866,3:1) |
| 两片接缝 | **y 367**(落在围墙 203–367 背后) | **y 734**(落在围墙 406–734 背后) |
| 两片接缝 | **y 367**(落在围墙 220–420 背后) | **y 734**(落在围墙 406–734 背后) |
| 地平线(山脚/草地起点) | y 与两片接缝重合,墙背后不可见 | 同左 |
| 远山带 | y 51–202(画面 14%–55%,山体 41%) | y 103–403 |
| 草地带 | y 202–367 | y 403–734 |
| 围墙层外接矩形 | y 203–367(高 164) | y 406–734(高 328) |
| 围墙带(wall 层桩身) | y 217–367(**高 150px**) | y 434–734(**高 300px**) |
| 门柱(wall 层,略高) | y 203–367(高 164);x 500–520 / 760–780 | y 406–734;x 1000–1040 / 1520–1560 |
| 门洞(wall 层透明缺口) | y 283–367(高 84);x 520–760 | y 566–734(高 168);x 1040–1520 |
| 围墙层外接矩形 | y 220–420(高 200,2026-11-07 裁) | y 406–734(高 328,素材画布不变) |
| 围墙带(wall 层桩身) | 素材桩身 150 按 200/164 显示拉伸(显示约高 183) | y 434–734(**高 300px**) |
| 门柱(wall 层,略高) | 显示 y 220–420(高 200);x 500–520 / 760–780 | y 406–734;x 1000–1040 / 1520–1560 |
| 门洞(wall 层透明缺口) | 墙件缺口全高 y 220–420;门扇可见 y 282–420(高 138);x 520–760 | y 566–734(高 168);x 1040–1520 |
| 门洞内小路(画在远景片草地内) | x 520–760 穿门洞,自接缝 y367 向上收窄,到山脚 y202 消失 | x 1040–1520,自 y734 向上收窄,到 y403 消失 |
---
@@ -147,7 +147,7 @@ Do not add any of the following: realistic, photorealistic or 3D rendering; pain
| 笔记本 | 5 | 100×80 | 200×160 | §3.7 |
| 篝火 | 6 | 160×160 | 320×320 | §3.8 |
**落位尺寸(2026-11-05 用户裁地平线 y367 重排后)**:布告板 130×121/门扇 240×138/木桩 120×180/锻炉 240×193/帐篷 251×170/宝箱 106×90/笔记本 100×66/篝火 160×149——贴图锚定=底中心(帐篷/锻炉/宝箱/篝火/笔记本)+顶锚(门扇悬挂/布告板挂墙);命中矩形按锁定值。八件只改落位,门扇另随门洞压扁重画(原 240×211 → 240×138),其余七件沿用现有终稿。
**落位尺寸(2026-11-05 用户裁地平线 y367 重排后)**:布告板 130×121/门扇 240×138/木桩 120×180/锻炉 240×193/帐篷 251×170/宝箱 106×90/笔记本 100×66/篝火 160×149——贴图锚定=底中心(帐篷/锻炉/宝箱/篝火/笔记本)+顶锚(门扇悬挂/布告板挂墙);命中矩形按锁定值。八件只改落位,门扇另随门洞压扁重画(原 240×211 → 240×138),其余七件沿用现有终稿。**2026-11-07 追加**:布告板 `(845, 241)`、门扇 `(520, 282)`、围墙显示矩形与墙脚入土见 §5 第 12 条。
**3.1 布告板(→ 图鉴)**
```
@@ -213,7 +213,7 @@ A campfire made of a ring of grey stones around crossed stacked logs with a warm
- 目检:细节由大像素块构成、块尺寸与远景片同一档,读作拖拽脚印/碎石/踩踏斑块;禁 1–2px 碎点噪声、禁迷彩式闭合斑块
**整图(两片拼接后)**
- 接缝 y367 落在围墙 203–367 背后,**画面里看不见接缝**
- 接缝 y367 落在围墙 220–420 背后,**画面里看不见接缝**
- 单张 ≤32 色(量化后)
- 全幅无任何独立物件/图标/徽章/标记
@@ -238,12 +238,13 @@ A campfire made of a ring of grey stones around crossed stacked logs with a warm
1. **地平线与整体分带**:地平线定在终稿 y367(出图 y734);地面 433px、可见远景 217px(27%);围墙、门、布告板随之上调
2. **围墙高度**:桩身 150(出图 300)、门柱 164(出图 328)、门洞 84(出图 168);墙高须大于门洞高
3. **门扇尺寸**:贴图 240×138(锁定 240×147),随门洞压扁重画,坐标按门洞 x520–760 对齐
3. **门扇尺寸**:显示矩形 240×147(贴图可见高 138),门扇件沿用现 240×211 由运行时压扁(2026-11-07 用户裁「不动美术素材」),x 对齐门洞 520–760、底压墙底 → (520, 282)
4. **帐篷**:251×170 沿用现终稿,落位 (982, 335),遮住墙下部 32px(覆盖围墙可接受)
5. **笔记本**:随帐篷跟随,落在帐篷左下方外侧、两者不重叠——`x = 帐篷 x − 112`、`y = 帐篷底 + 10`
6. **宝箱离底**:贴图底 y744,距画面底 56px;其余物件同步上移 46px
7. **地面分两区**:营地外(y734 以上)=晒白的浅黄绿草地+一条向远山收窄的暖米白土路;营地内(y734 至画面底)=正午太阳下晒到泛白的踩实米黄土地、与草地异色相、无草无路无车辙;两区分界被围墙层完全遮住
8. **背景层分两片出图**:远景片 `bg_far`(2560×734,天空 14%/远山 41%/草地 45%+小路)+ 地面片 `bg_floor`(2560×866 踩实裸土),后处理按 y734 拼成一张 2560×1600;接缝落在围墙 406–734 背后。分片理由=整张出图时模型把山挤到画面下半部,实测三张里两张山体整段被围墙遮没
9. **布告板位置**:(845, 224, 130, 140),底 345 落在墙内,不再悬空
9. **布告板位置**:(845, 241, 130, 121)(2026-11-07 随墙顶),底 362 落在墙内(墙 220–420);命中 130×140
10. **描边颜色**:奶油色追认——去底脚本「奶油描边膨胀 3px 当屏障」的技术前提随现值锁定(§1 注记同源);改深灰褐须换去底方案全量重出,不裁
11. **营地环境子板色值**:不前置拍板——量化场景实际用色后按 art-bible §4.1 两道门登记(语义检查+RGB 三通道差 ≥8/255),本卡仍不复述色值
12. **围墙显示几何与墙脚入土(2026-11-07 用户裁)**:围墙显示矩形 `(0, 220, 1280, 200)`(顶 y220、高 200、底锚 y420);大门 240×138 尺寸不变、底压墙底 → `(520, 282)`;告示板 130×121 尺寸不变、随墙顶下移 17px → `(845, 241)`;墙脚按 1–7px 低频起伏裁透明缺口透出地面纹理(入土线,判据归 art-bible §6.2)
@@ -72,6 +72,14 @@
| 换装券 | 冒险中可在节点处换装一次 | 终裁装配只在冒险开始前配置、冒险中零更换途径——无此件存在空间 | 2026-10-11 |
| 第四只手 | 同调位 +1(额外格=优秀格) | 净收益为零的自指件——占一槽换一槽(真实携带量 3=3)且自锁唯一稀有格 | 2026-10-11 |
## 表五 重设计件退役(2026-11-07 起)
已实装 `feats.tres` 后退役的重设计专长,留底供日后重新过准入(回收须过准入校验/分界尺/定义卡三关,与其他素材库件同规)。
| 代号 | 效果 | 退役原因 | 退役日期 |
|---|---|---|---|
| 破封一击 | on 挖开有封印印记的危险格:额外 +1 层(疼痛记忆同账) | 触发路径删除——「封印受理即揭示、封印格拒绝挖掘」口径换代后「挖开封印格」不复存在,专长永远无法触发(2026-11-07 用户裁) | 2026-11-07 |
## 备选素材(未立项提名,2026-10-10 用户令增设)
> 本节收纳**曾被提名但从未立项、零设计细则**的备选概念——只记提名出处与当前结构前提,不做详细设计;是否立项、立项后怎么设计,待用户另议。与退役表不同:这些件从未落过数据表或正文设计,回收时无「原效果」可考,须从头过定义卡。
+96 -255
View File
@@ -2,21 +2,20 @@
## 文档状态
- 版本:1.37
- 版本:2.0(收窄为约束面形态——分层/模块归属/数据流/API 边界;实现现状归代码与代码头注,实现历史归 git commit log,闸清单与判定口径归 `game/tests/README.md`,见 doc-conventions「架构事实唯一源」行)
- 引擎:Godot 4.7.2-stable(GDScript 单语言;2026-09-07 锁定)
- 评审模式:solo(`review-mode.txt`,2026-09-12 用户拍板——TD/LP 门禁按 solo 跳过,用户授权架构产出自主推进、迭代期回调)
- 评审模式:solo(`review-mode.txt`——门禁按 solo 跳过)
- 覆盖 GDD:现行全集(根索引=`design/gdd/02-系统清单.md`,文档树构成以根索引为准)
- 事实源地位:本文是架构事实的唯一来源——分层/所有权/数据流/API 边界/测试与质量域均以本文为准;历史决策沿革查 git commit log,不另设决策文档体系
- 实现实证:`game/`(2026-09-10 起开发,**现为完整游戏开发版本,已非垂直切片**;测试层级=冒烟/单元/系统三级+UI 探针与渲染 golden(2026-10-22 用户裁立;2026-11-07 用户裁层级合并,跨系统闭环套件归单元层),闸机按改动域分跑在役)——本文档的分层/所有权/数据流均经实现验证,非纯纸面设计
- 主程序可行性:跳过 — solo 模式
- 事实源地位:本文是架构约束的唯一权威(分层/模块归属/数据流/API 边界);写权限=用户专属(agent 与技能只读)
- 实现实证:`game/`(完整游戏开发版本;本文分层与所有权经实现验证)。测试层级=冒烟/单元/系统三级+UI 探针与渲染 golden,识别三元组权威=`design/engineering/test-conventions.md`「测试层三元组」节
---
## 引擎知识缺口摘要
- 引擎事实现行事实源=`design/engineering/engine-reference/godot/`(VERSION / breaking-changes / deprecated-apis / current-best-practices 四件)——迁移核实结论、语义变化与编码注意一律查那里,本文不重复。
- 渲染器与窗口基座=gl_compatibility + 1280×800(2026-09-12 裁);UI/渲染域为引擎风险集中地,采纳引擎增量前对照 engine-reference 核实。
- 写码纪律不变:不确定的 API 一律查 `docs.godotengine.org/en/4.7/` 核实后再用;4.8(dev 阶段)稳定版发布后跑 `/setup-engine refresh`。
- 渲染器与窗口基座=gl_compatibility + 1280×800;UI/渲染域为引擎风险集中地,采纳引擎增量前对照 engine-reference 核实。
- 写码纪律不变:不确定的 API 一律查 `docs.godotengine.org/en/4.7/` 核实后再用。
---
@@ -31,7 +30,7 @@
│ D4 音频 (Beta) · D5 教学引导 (Beta) │
├────────────────────────────────────────────────────────────┤
│ 功能层 │
│ B1 战斗结算编排 (MVP) · C1 关卡节点地图 (lite 已实装) │
│ B1 战斗结算编排 (MVP) · C1 关卡节点地图 │
│ C2 冒险结构 · C3 难度曲线 · C4 元进度 (Alpha) │
├────────────────────────────────────────────────────────────┤
│ 核心层 │
@@ -61,13 +60,13 @@
| B2 敌人系统 | 核心层 | MVP | ✅ 完整可跑 | LOW(纯逻辑) |
| B4 地图战斗资源 | 核心层 | MVP | ✅ 完整可跑 | LOW(纯逻辑) |
| B1 战斗结算编排 | 功能层 | MVP | ✅ 完整可跑(禁 await) | LOW(纯逻辑) |
| C1 关卡节点地图 | 功能层 | MVP(切片期实装) | ✅(切片 level_runner 先例) | LOW |
| C2 冒险结构 | 功能层 | MVP(切片期实装) | ✅ | LOW |
| C3 难度曲线 | 功能层 | MVP(切片期实装) | ✅ | LOW |
| C1 关卡节点地图 | 功能层 | MVP | ✅ | LOW |
| C2 冒险结构 | 功能层 | MVP | ✅ | LOW |
| C3 难度曲线 | 功能层 | MVP | ✅ | LOW |
| C4 元进度 | 功能层 | Alpha | ✅ | LOW |
| D1 HUD | 呈现层 | MVP | ❌(UI 层——场景级探针测试承载) | **MEDIUM**(Control/Input/文本域) |
| B5 战斗表现 | 呈现层 | 切片后立项(未实装,前置战斗表现美术测试) | ❌ | **MEDIUM**(Animation/Tween/渲染) |
| D4 音频 / D5 教学 | 呈现层 | Beta(2026-10-13 Alpha 版本标准裁:与游戏内容无关者一律排 Beta) | 部分 | LOW-MEDIUM |
| D4 音频 / D5 教学 | 呈现层 | Beta(与游戏内容无关者一律排 Beta) | 部分 | LOW-MEDIUM |
| D3 存档序列化 | 基础层 | Alpha | ✅ | LOW |
| D6 设置/本地化 | 基础层 | 完整愿景 | ✅ | LOW |
@@ -77,142 +76,88 @@
## 模块归属
> 命名遵循 `design/engineering/code-conventions.md`:领域命名、禁 ID 前缀(文件头注不写文档链接——「实现 GDD:xxx」一类已废止,代码↔设计追溯由 GDD 文档承担)。
> 命名遵循 `design/engineering/code-conventions.md`:领域命名、禁 ID 前缀。本表只写约束:模块的职责语句、对外封闭面概述与消费依赖——各件的完整公开方法面、成员与接线明细归代码文件头注(`##` 文档注释)与代码自身承载;文件路径是导航索引。
### 基础层
| 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API |
| 模块 | 文件 | 独占职责 | 对外封闭面 | 消费依赖 |
|---|---|---|---|---|
| **ConfigDB**(autoload,D2) | 全部玩法数值表与表现层布局配置表(`data/tables/*.tres` + `data/schemas/*.gd`);加载/校验/override/诊断状态机。**行族访问器归位各 schema 表类(表管自己的行查询),本类只留加载/校验/override/诊断门面;表持有成员按域收进四张域容器 Resource(`config_domain_combat/run/crossrun/presentation.gd`——四张域容器均已建件,类型化成员 7/7/5/1);三张 resolver 表(board_gen.resolve_row/enemy.resolve_rowset/difficulty.resolve_modifiers)经 `load_tables` 装配段注入各容器,resolver 文件与键零搬移**——红线不变:本类仍是唯一加载与持有单点,"经 ConfigDB 单点读取"=经其持有的表对象 | 类型化域容器属性(每域一张容器);公开面 8(push_diag/is_fatal/reset_diagnostics/diagnostics/diagnostics_count/load_tables/set_reload_gate/reload)、类变量 10 | 无(基础层顶点) | Resource/.tres 加载、autoload 生命周期——LOW |
| **SlotStore**(`core/slot_store.gd`,D3 存档槽位域件,已建件) | 存档三槽位文件域唯一所有者:槽位挂载与模板、激活与建删、槽位摘要窥视(peek 族);改名覆盖原语(_rename_overwrite)留 save_manager(IO 原语单点) | 槽位读写原语族+摘要投影(公开 11:active_path 读投影、migrate_legacy_save 公开化;对 SaveManagerCore 反向私取 21 处清零=双向公开面;类变量 4;SaveManager 与菜单消费方经 `save.slots.` 直连) | save_manager(注入路径与模板配置);FileAccess——LOW |
| **MetaStateStore**(`core/meta_state_store.gd`,D3 存档内元进度数据件,已建件) | 存档内元进度数据的读写唯一层(钥石/道具解锁/树节点与叉侧/锻造路线/同调/真视/计数器/摘要——数据读写原语,不含业务规则);save_manager 与 meta_progression 双 God Class 的共同病根=该数据域摊在两文件,本件即其归一 | 写口原语 13+只读投影 6(tree_state/forge_state/attune_state/career_state 四投影+keystone_balance/item_unlocked)=公开 19(add_keystone 私有 `_add_keystone`),零 _data 副本零缓存;MetaProgression 业务层与营地/锻造 UI 消费方经 `save.meta_state.` 直连(meta 的纯转发读方法已退役) | save_manager(序列化接线);FileAccess——LOW |
| **MetaProgression**(autoload,`core/meta_progression.gd`,D3 元进度业务层) | 元进度账本服务:钥石收束入账、营地消费定价与秘宝解锁判定的纯被动服务,零信号零外发;数据读写全经注入的 MetaStateStore(`save.meta_state.` 直连);hero_override() 现算不写表(全零=白板合法态——生效注入只走冒险开始参数面显式传参) | 写口族(天赋树购买/叉侧切换/锻造解锁与升级与选中/同调装配切换等)+解锁判定与读投影;**构造双形态**=autoload 装配走 `.new()` 零参+`_ready` 从 autoload 链取依赖(工程序 ConfigDB→RngService→SaveManager→MetaProgression,注册顺序即依赖序),单测/工具走 `.new(db, save)` 直构形态(不入树零 _ready)——**四个 Autoload 中唯一有跨 Autoload 依赖的件(ConfigDB/SaveManager)** | ConfigDB(定价与档位表值);SaveManager(存档访问器,经节点路径延迟取用) | autoload 生命周期——LOW |
| **RngService**(autoload,D2) | master_seed、各流 scope 计数器(封闭流词表现值以 `RngServiceCore.PURPOSE_TAGS` 为准;增流走 P1 评审) | `set_master_seed()`(唯一调用方 C2);`open_stream(stream)` 有状态门面;`derive_at(stream, scope)` 纯函数;只读计数器 | ConfigDB(无) | 无——纯逻辑 |
| **action_economy**(A3) | AP/RAP 账本、last_round_end、轮次相位机 {Idle, Active, Waiting}、结束仲裁;dig_paid 命中施加抽私有单点(_apply_dig_paid_hits——矿脉储能入池面) | 意图与通知布尔面:notify_round_start / request_dig / request_stop / request_refresh(收折后价入参——折价计算归 BattleRefreshFlow 单点)/ notify_settlement_done / notify_combat_end;预授权通道:grant_reserve_ap / finish_round_stop;ap/rap/phase 只读;实测类变量 10 | ConfigDB(ActionEconomyTable);EffectPort(效果端口件——发射经端口单点,effect_engine 直连退役);B1 回发 next_round_mode/battle_over | 无 |
| **variation 定义**(A4) | VariationTable schema(现行 6 行:六 region——贫矿脉 global 行已屏蔽删行,global 型机制代码保留)与 F1 候选池门控谓词 | `variant_pool(level_index, board_type, node_type="")`(门控三维:unlock+board_types+节点身份——global 行仅 boss/elite 入池,缺省跳过零扰动);`global_variant_row(level_index, board_type, node_type)`(全局型单行决议访问器) | ConfigDB(VariationTable) | 无 |
| **status_container**(B3) | 状态实例容器(战斗域生命周期、跨轮跨图延续) | 5 API:notify_battle_start / apply_effect / query_active / decay_round / notify_battle_end | ConfigDB(StatusEffectTable) | 无(RefCounted) |
| **effect_engine** | 零运行时可变状态的系统(见「架构原则」条 8):全同步解释器——事件由宿主在明确代码点位显式调 dispatch 进件,命中序=装载声明序(feats→treasure→树规则)写死保种子复放,不是事件总线/事件循环;事件/事实/效果类型/算子四词表封闭 + 树规则合成订阅六键(posture 每关首轮窗/sense 两组限次 counter 闸·adv_ 前缀走冒险桶跨战斗持久) | rebuild(每战重建订阅表)/`dispatch(ctx, event, payload, facts)`/`validate_trigger_row` + 静态计数口 `counter`·`counter_add`·`counter_spend`·`bucket_of`·`counter_id_legal`(计数器读写只经 EffectContext 引用束,无导出/灌入接口) | ConfigDB(feats/treasure 行集)、EffectContext(四桶引用由宿主持有:round/battle 桶归编排器、level 桶归关卡域、adventure 桶归应用层——RunLedger 已退役,桶字典由 adventure_runner 持有) | 无(零随机全确定) |
| **save_system**(D3,Alpha;已升格 autoload「SaveManager」) | 存档格式与版本比较 | save_manager 公开面 14(set_read_only/read_only/reset_to_default/data_snapshot/restore_data 五承接方法+peek_summary_at/rename_overwrite 两更名公开化);(契约规范源:`../../gdd/systems/表现与支撑/存档序列化.md`——契约锚:SAVE_FORMAT_VERSION + master_seed + 各流 scope 计数器快照,须在无正在执行中的 open_stream 实例时切出) | ConfigDB、RngService 只读计数器 | FileAccess——LOW |
| **settings/localization**(D6,愿景) | 设置项、翻译数据 | (契约规范源:`../../gdd/systems/表现与支撑/设置与本地化.md`) | ConfigDB | 无 |
| **ConfigDB**(autoload,D2) | `game/core/config_db.gd` | 全部玩法数值表唯一加载与持有单点(`.tres` 经校验后入容器);行族访问器归各 schema 表类(表管自己的行查询),本类只留加载/校验/override/诊断门面,表持有按域收进域容器 Resource | 类型化域容器属性+load_tables / reload / set_reload_gate / diagnostics 族 | 无(基础层顶点) |
| **RngService**(autoload,D2) | `game/core/rng_service.gd` | master_seed 与各封闭流 scope 计数器单点(封闭流词表现值以 `RngServiceCore.PURPOSE_TAGS` 为准,增流走 P1 评审) | set_master_seed(唯一调用方 C2)/ open_stream 有状态门面 / derive_at 纯函数 / 只读计数器 | 无 |
| **save_system**(SaveManager,autoload,D3) | `game/core/save_manager.gd` | 存档格式与版本比较、序列化接线单点;槽位文件域归 SlotStore、元进度数据归 MetaStateStore | 写盘/读回/槽位管理/断点快照公开面(契约规范源:`../../gdd/systems/表现与支撑/存档序列化.md`——契约锚:SAVE_FORMAT_VERSION + master_seed + 各流 scope 计数器快照) | ConfigDB、RngService 只读计数器 |
| **SlotStore**(D3) | `game/core/slot_store.gd` | 存档三槽位文件域唯一所有者:槽位挂载与模板、激活与建删、摘要窥视 | 槽位读写原语族+摘要投影 | save_manager(注入路径与模板配置) |
| **MetaStateStore**(D3) | `game/core/meta_state_store.gd` | 存档内元进度数据的读写唯一层(数据读写原语,不含业务规则) | 写口原语族+只读投影(消费方经 `save.meta_state.` 直连) | save_manager(序列化接线) |
| **MetaProgression**(autoload,C4) | `game/core/meta_progression.gd` | 元进度账本服务:钥石收束入账、营地消费定价与解锁判定的纯被动服务,零信号零外发;业务层(数据读写全经 MetaStateStore) | 写口族(天赋树/锻造/同调)+解锁判定与读投影;四个 Autoload 中唯一有跨 Autoload 依赖(ConfigDB/SaveManager) | ConfigDB、SaveManager |
| **action_economy**(A3) | `game/core/action_economy.gd` | AP/RAP 账本、轮次相位机 {Idle, Active, Waiting}、结束仲裁 | 意图与通知布尔面(request_dig / request_stop / request_refresh / notify_* 族)+预授权通道;ap/rap/phase 只读 | ConfigDB、EffectPort、B1 回发 |
| **variation**(A4) | `game/core/variation*.gd` | VariationTable schema 与 F1 候选池门控谓词 | variant_pool(门控三维)/ global_variant_row 访问器 | ConfigDB |
| **status_container**(B3) | `game/core/status_container.gd` | 状态实例容器(战斗域生命周期、跨轮跨图延续) | 五 API:notify_battle_start / apply_effect / query_active / decay_round / notify_battle_end | ConfigDB |
| **effect_engine** | `game/core/effect_engine.gd` | 零运行时可变状态的全同步解释器(架构原则 8):事件由宿主在明确代码点位显式 dispatch 进件,命中序=装载声明序,不是事件总线/事件循环 | rebuild / dispatch / validate_trigger_row +静态计数口;事件/事实/效果类型/算子四词表封闭+树规则合成订阅六键 | ConfigDB 行集、EffectContext 引用束(四桶由宿主持有) |
| **settings/localization**(D6,愿景) | — | 设置项、翻译数据 | (契约规范源:`../../gdd/systems/表现与支撑/设置与本地化.md`) | ConfigDB |
### 核心层
| 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API |
| 模块 | 文件 | 独占职责 | 对外封闭面 | 消费依赖 |
|---|---|---|---|---|
| **board_generator**(A1) | 生成算法(五阶段管线)、BoardGenTable 回填、生成期指标集、合法性谓词(clearable / R₀≠S / min_safe)、可推理率度量(metric_version);生成姿态 = MVP 同步生成 + 加载幕(切片实证 10×10/12×12 同步可接受,WorkerThread 留扩展) | `generate(L, D, rng) -> Board`;度量独立入口×3(推理器 / F3 度量 / 合法性谓词——独立纯函数、同一实现) | ConfigDB(BoardGenTable/VariationTable);board_gen 流(一次 open_stream = 一张图);dig_ap_cost 只读 | 无 |
| **minesweeper_rules**(A2) | 棋盘状态(已开集 O / 封印集 G / 小人位)、三事件面、批次仲裁、install 四不变式 | `install_board(board)`;`settle_batch(intents)`(唯一提交入口);查询只读面(current_board / hero_pos / is_revealed / has_seal 等);`compute_move_path(cell)` 只读;`preview_reveal(cell)` 只读预览口(零写入零事件——0 格连锁传播闭包);`retire_board()`;+harmful_variants/harmful_adjacent 两有害判定面(编排器归位);三源拒绝(board_face / resource_face / phase)+ sub_reason 封闭枚举;公开方法面 20(`safe_remaining` 已私有化为 `_safe_remaining`,唯一生产消费点=同类 `clear_map_cleared`),gdstyle S13 归绿零基线键 | A1 产物(Board class_name 数据契约);A3 try_spend / last_round_end/ 相位 | 无(零表读取、零 UI——切片实证) |
| **unit_container**(B2) | hero/enemy 单位实例(hp/atk/实例池:池快照+冷却+摇定动作+保底计数)、行值快照 | spawn / query_alive / rollable_skills / special_skills / check_trigger_unlocks / set_pending_action / pending_action / basic_streak / commit_action / tick_cooldowns / apply_damage / heal / default_target / check_skill_unlocks / despawn_all(query_beat/query_attack/advance 已随行为环退役;rollable_skills/special_skills/check_trigger_unlocks 带 facts 参——情境条件触发器在役,事实缺键=条件假拦下,百分比事实走阈值式折算) | ConfigDB(EnemyTable/SkillTable/HeroTable) | 无 |
| **reward_economy**(B4) | 格面资源身份映射(install 期预分配、Ready 只读、换图映射消亡)、极性路由字典面((类型, 极性)→ 字典行查询;路由执行归 B1 授予段)、F6 当量供给投影;装配期摇号面已拆出掷格件 RewardRoller(roll_board 装配序=守卫→复位→委托→收纳→ready;受理账面留守) | 封闭 API:roll_board / query_identity / query_full_snapshot / grant / grant_clear / retire_mapping / polarity_row / phase(weighted_pick 退役——返行选行算子迁掷格件);另有 is_mapped 静默布尔投影(批量工具用);实测类变量 11 | ConfigDB(RewardTable);BattleFxHost 经注入 Callable `luck_bias_query` 取偏运合计(reward_roll 发射与 bias_rewards 求和移至效果层完成);reward_roll 流(scope = 每次 install 批,取号经掷格件) | 无 |
| **helper_items**(A5,切片已实装秘宝域) | HelperItemTable、探测显影语义 | (契约规范源:`../../gdd/systems/跨局循环/秘宝.md`——桥接位:A2 查询接口 + A4 counter_item_ids) | A2 / A4 / B4 | 无 |
| **CurioDeck**(`core/curio_deck.gd`,A5 秘宝域令牌池件) | 三选一/商店共享池的持久多重集、抽签(候选抽取)、cap 库存与专长凭证三计数与判定唯一所有者——件只管池与判定,停点编排与兑付推进留冒险编排器 | `draw_candidates(channel)` 抽签;`consume_deck_token` / `consume_pool_purchase` 池消耗;`cap_ok` 上限判定;`feat_ticket_consider` 凭证判定;`stock_snapshot` / `pool_stock` 只读投影;商店购买消费经注入 Callable 链至 CurioStop 兑付单点(已转纯池——bind_handlers 反向回调已退役) | ConfigDB(秘宝令牌行集);注入 RngServiceCore(reward_pick 封闭流,禁自建随机) | 无(RefCounted 纯逻辑) |
| **ShopSession**(`core/shop_session.gd`,商店会话件) | 关卡商店双模式(legacy 固定三件加定价/pool 共享令牌池)的货架、定价、购买与刷新唯一宿主——账本/钱包/装配集归宿主 LevelRunner(共享引用经 `bind` 注入,出账直接落同一引用改写宿主账本);legacy 模式零 RNG 零池消费(off 对照承载) | 货架与定价只读面(`shop_products` / `shop_shelf` / `current_price` / `shop_refresh_cost`)+写入口(`buy_product` / `buy_pool_item` / `shop_refresh` / `player_request_refresh` / `open_pool`);外部调用面=调用方经宿主 LevelRunner 公开成员 `shop` 直连(调用直连不留委托壳,2026-10-26 Middle Man 裁定),直连消费的方法=`shop_products`/`current_price`/`purchased_tier`/`buy_product`/`player_request_refresh`/`set_shop_pool`/`pool_price`/`shop_shelf`/`shop_refresh_free_left`/`shop_refresh_cost`/`shop_refresh`/`spend_gp`/`buy_pool_item`/`is_shop_pool`/`shop_bp_shelf`/`shop_bp_visit_buys`(另白盒两入口仅测试侧:`_draw_shelf`/`_spend_discounted`);set_shop_pool 调用面=pool 注入经 LevelRunner 构造 ctx 供给(构造期内部直调),测试面直连 `shop.set_shop_pool` | ConfigDB(商店行集);注入 RngServiceCore;宿主钱包/账本引用;pool 模式令牌消耗与判定经注入 Callable 转 CurioDeck | 无 |
| **HeroStats**(`core/hero_stats.gd`,主角数值账件,已实装) | 冒险生命周期主角数值域唯一所有者:生命账与特性持有集(活引用注入——关内兑付当关生效)、锻造强化快照、调试百分比通道、攻击/甲类型轴快照、生效上限/攻击/护甲公式与回血封顶公式单点、续玩生命钳制收口;生效公式经注入 GainJournal 只读引用取加算区汇总(无反向引用不成环) | 构造注入;单写原语族+只读投影;断点自交片段(账本键) | ConfigDB(表值读取);注入 GainJournal(只读);由 AdventureRunner 冒险开始创建、注入战斗 encounter | 无(RefCounted 纯逻辑) |
| **GainJournal**(`core/gain_journal.gd`,加算日记件,已实装) | 统一冒险加算区与获得记录唯一所有者:获得记录三层结构=记录列表(三选一件/商店购买/战末奖励各一条)→属性增益明细(带来源指回)→四轴汇总(由明细同步维护);三写通道统一走 gain_bonus 单写原语(source_class=curio_pick/shop_product/battle_export);本局钥石增量与逐杀明细、真视使用记录、商店已购档数记账同域 | gain_bonus 单写原语;记录与汇总只读投影;断点自交片段(获得日记键) | 由 AdventureRunner 冒险开始创建、注入 CurioStop/level_runner/ShopSession/战斗 encounter(战末 bonus 四轴经 gain_bonus 汇入) | 无(RefCounted 纯逻辑) |
| **GpWallet**(`core/gp_wallet.gd`,GP 钱包件,已实装) | 冒险局内货币唯一所有者(LevelRunner 与 ShopSession 读写全指此单点;冒险开始=0、收束清零,不入存档不入摘要) | gain/spend/clear 单写原语 | 由 AdventureRunner 冒险开始创建;宿主与商店经注入引用读写 | 无(RefCounted 纯逻辑) |
| **RefreshBank**(`core/refresh_bank.gd`,刷新账件,已实装) | 三选一刷新三源账唯一所有者:X 刷新件每碰三选一重给(优先消耗不结转)/Y 营地升级行冒险周期池(用完即无)/刷新券存量账(冒险级跨停点持久)+GP 付费刷计数与报价公式;零停点态依赖(免费/付费支出的编排归 CurioStop,本件只记账) | 支出与余量原语+报价公式;断点自交片段(refresh_stock/attune_refresh_y 两键,Y 缺键回退参数面) | ConfigDB(刷新件与营地升级行表值);注入 CurioStop 支出路径 | 无(RefCounted 纯逻辑) |
| **AttuneLoadout**(`core/attune_loadout.gd`,同调配装件,已实装) | 装备中的秘宝件对象集唯一所有者(每件内含充能单位与重置周期);同调充能的物化行为唯一事实源——种行/补行改写时同步(RunLedger 充能字典轨与差额对账函数退役);旧档 aux_items 同调行迁移归件 | 构造注入;充能查询/消耗/按周期重置原语;断点自交片段(attunement_charges 键,键名与策略不变) | ConfigDB(秘宝行集);TreasureItems(同调行投放面);注入战斗使用路径 | 无 |
| **CurioStop**(`core/curio_stop.gd`,三选一停点件,已实装) | 三选一停点编排唯一所有者(架构原则 9 本域承载):停点三态(关卡完成三选一在途/已兑付待推进/奖励节点三选一在途)、刷新编排(三源账面归 RefreshBank 件、查停点态→支出→重抽重发编排留本件)、兑付方法族(pick/refresh/redeem/skip)、凭证出箱(RAP/时沙待注入);三选一信号面自带;零自动推进停点语义承载 | 对外=开停点/刷新/兑付/放行谓词/交断点片段五组入口+信号面(curio_choice_presented/curio_resolved);由 AdventureRunner 关卡完成受理时调用 | ConfigDB;注入 CurioDeck(抽签/摘牌)、HeroStats 与 GainJournal(兑付落账单写原语)、RefreshBank(刷新支出);随机只经 CurioDeck 的 reward_pick 封闭流 | 无 |
| **breakpoint_store**(`core/breakpoint_store.gd`,冒险域·断点组装与回放件,已建件) | 存档断点组装与回放唯一承载:断点快照拼盘、回放回填、读侧策略分发;`BREAKPOINT_FIELDS` 27 键登记单源随件(键名/策略标签/键序冻结) | 构造=引用束注入(九引用束成员解包为原名成员——保迁移函数体逐字);`resume_adventure` 留宿主(壳+宿主侧回填语句——R2 所有权缝分家);断点键面零变化;公开方法面 18(13 新面承接 adventure_runner 私取清零+bp_read 公开化);实测类变量 15(贴顶禁再增) | adventure_runner(宿主);注入主角账三件与 curio/stop/bank/loadout 引用 | 无(RefCounted 纯逻辑) |
| **board_generator**(A1) | `game/core/board_generator.gd` | 生成算法(五阶段管线)、合法性谓词(clearable / R₀≠S / min_safe)、可推理率度量;生成姿态=MVP 同步生成+加载幕(WorkerThread 留扩展) | `generate(L, D, rng) -> Board`;度量独立入口 | ConfigDB;board_gen 封闭流(一次 open_stream=一张图) |
| **minesweeper_rules**(A2) | `game/core/minesweeper_rules.gd` | 棋盘状态(已开集/封印集/小人位)、批次仲裁、install 四不变式 | `install_board` / `settle_batch`(唯一提交入口)/ 查询只读面 / preview_reveal 只读预览口 / retire_board;三事件封闭(cell_revealed / flag_judged / round_end,payload 字段封闭) | A1 Board 契约(class_name 数据契约);A3 try_spend |
| **unit_container**(B2) | `game/core/unit_container.gd` | hero/enemy 单位实例(hp/atk/实例池) | spawn / query_alive / 动作与伤害面 | ConfigDB |
| **reward_economy**(B4) | `game/core/reward_economy.gd` | 格面资源身份映射(install 期预分配、换图映射消亡)、极性路由字典面、受理账面(装配期摇号归掷格件 RewardRoller) | 封闭 API:roll_board / query_identity / grant / grant_clear / retire_mapping / polarity_row / phase | ConfigDB;BattleFxHost 偏运合计查询(注入 Callable) |
| **helper_items**(A5) | `game/core/helper_items*.gd` | HelperItemTable、探测显影语义 | (契约规范源:`../../gdd/systems/跨局循环/秘宝.md`——桥接位:A2 查询接口+A4 counter_item_ids) | A2 / A4 / B4 |
| **CurioDeck**(A5) | `game/core/curio_deck.gd` | 三选一/商店共享令牌池、抽签、cap 库存与专长凭证判定唯一所有者(停点编排与兑付推进留 CurioStop) | draw_candidates 抽签 / consume 族池消耗 / cap_ok / 凭证判定 / stock 只读投影 | ConfigDB;注入 RngServiceCore(reward_pick 封闭流) |
| **ShopSession** | `game/core/shop_session.gd` | 关卡商店双模式(legacy 固定三件/pool 共享令牌池)的货架、定价、购买与刷新唯一宿主;账本/钱包经宿主 LevelRunner 注入共享引用 | 货架与定价只读面+写入口;调用方经宿主公开成员 `shop` 直连 | ConfigDB;注入 RngServiceCore;pool 模式经注入 Callable 转 CurioDeck |
| **HeroStats** | `game/core/hero_stats.gd` | 冒险生命周期主角数值域唯一所有者:生命账与特性持有集、生效公式单点 | 构造注入;单写原语族+只读投影 | ConfigDB;注入 GainJournal(只读);AdventureRunner 创建 |
| **GainJournal** | `game/core/gain_journal.gd` | 统一冒险加算区与获得记录唯一所有者(三层结构:记录→明细→四轴汇总) | gain_bonus 单写原语;记录与汇总只读投影 | AdventureRunner 创建,注入各消费方 |
| **GpWallet** | `game/core/gp_wallet.gd` | 冒险局内货币唯一所有者(不入存档不入摘要) | gain / spend / clear 单写原语 | AdventureRunner 创建,宿主与商店注入读写 |
| **RefreshBank** | `game/core/refresh_bank.gd` | 三选一刷新三源账唯一所有者+GP 付费刷报价公式(零停点态依赖,编排归 CurioStop) | 支出与余量原语+报价公式;断点自交片段(refresh_stock / attune_refresh_y) | ConfigDB;注入 CurioStop 支出路径 |
| **AttuneLoadout** | `game/core/attune_loadout.gd` | 装备中的秘宝件对象集与同调充能唯一所有者(种行/补行改写时同步) | 构造注入;充能查询/消耗/按周期重置原语 | ConfigDB;TreasureItems 投放面;注入使用路径 |
| **CurioStop** | `game/core/curio_stop.gd` | 三选一停点编排唯一所有者(架构原则 9 本域承载):停点三态、刷新编排、兑付方法族、零自动推进语义 | 开停点/刷新/兑付/放行谓词/交断点片段五组入口+信号面(curio_choice_presented / curio_resolved) | ConfigDB;注入 CurioDeck、HeroStats、GainJournal、RefreshBank |
| **breakpoint_store** | `game/core/breakpoint_store.gd` | 存档断点组装与回放唯一承载:断点快照拼盘、回放回填、读侧策略分发 | `BREAKPOINT_FIELDS` 键登记单源(键名/策略标签/键序冻结) | 注入引用束 |
### 功能层
| 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API |
| 模块 | 文件 | 独占职责 | 对外封闭面 | 消费依赖 |
|---|---|---|---|---|
| **battle_orchestrator**(B1) | 段序骨架(七段结构冻结)、轮 facts 缓冲、可回放步骤流、[明细行协议](../../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)(基础行型+扩展行两层 + 字段分层,词表以 owner「明细流水」节为准)、条款③⑤闭合对账、**战斗域全部跨系统调用权**、效果引擎宿主面与树规则挂点施加面(**已拆出 BattleFxHost 并实装(2026-10-28 迭代 40——battle_fx_host.gd/reward_grant.gd 两件在库,效果引擎宿主面归 BattleFxHost 行承载,拆件细则见「战斗编排域拆件定稿」节);2026-10-29 零豁免裁设计闭合:RoundCycle/BattleFacts 两新件随批(宿主 38→15、S13 豁免第三条注销——见下两行与定稿节轴丁)**) | `begin_battle(encounter)`;窗口/刷新意图提交面(扮演 D1);`level_clear()`;步骤流(输出契约) | B2/B3/B4 全调用面(**唯一调用方=B1 域文件集:battle_orchestrator.gd + settlement/ 五段段类 + damage_controller + 拆件后三新件**);A3 request_refresh 代理 + 回发;A2 install/retire + 三事件订阅;A1 generate(attempt 非 scope 事件);D2 SettlementTable + set_reload_gate | 无(零随机、禁 await——切片 88 冒烟实证可回放) |
| **BattleFxHost**(`core/battle_fx_host.gd`,B1 域·效果引擎宿主件,已建件) | 效果引擎挂点施加面唯一承载:事件发射与事实判定(_fx_emit/_fx_fact)、主角 AC 基线与本轮/本战 AC 累积、bonus_delta 四轴累积(战末宿主 drain 汇入账本)、揭示待兑队列与兑付(含 _fx_revealing 再入闸——同步信号回环防二次缓冲二次兑付)、树规则合成订阅的 round/battle 桶计数器持有(桶随编排器生灭语义不变);效果端口件为业务类朝向机房的窄门面——端口在本件组建、bind_port 组装期转注两业务类,计数器账面收归本件;实测类变量 15 | 构造=引用束注入(防 _init max-parameters 新键);bind 时注入 emit Callable(行发射经宿主协议面单点)与兑付 Callable(RewardGrant 授予面——揭示队列 flush 回调);+mark_new_resource_type/+add_bonus_delta 两公开累积方法(grant 消费面——类变量 15 不变,方法-only);只读投影分两类消费方:AC 累积/bonus_delta 供宿主呈现快照与战末 drain,揭示态供宿主 A2 订阅旁路判定;`luck_bias_for(cell_count) -> int` 偏运合计查询口(体内三参形 dispatch——reward_roll 发射与 bias_rewards 求和自 RewardEconomy 收回本件,RewardEconomy 经注入 Callable 消费) | EffectEngine(EffectContext 引用束);RngServiceCore(fx_reveal 封闭流——除该流外零随机);A2 minesweeper(板读与 fx_reveal 调用);A3 action_economy(queue_ap_delta 读);B3 status_container 读;B2 unit_container 读(hp_ratio);DamageController(set_hero_ac_override);ConfigDB;encounter 引用 | 无(零 await——效果引擎全同步解释器) |
| **RewardGrant**(`core/reward_grant.gd`,B1 域·奖励授予件,2026-10-26 编排域拆件设计定稿——实现批建件) | 奖励授予唯一兑付管线(自 battle_orchestrator 收编 grant 族):极性字典路由与封印溢价兑付(_grant_routed)、三种行应用(flat/percent/gp)、伤害单据提交面(_submit 族)、清图奖励与身份授予、秘宝使用编排(use_treasure 四步)、战斗域 GP 缓冲 escrow 与授予段暂存伤害闸(_defer_board_damage/_deferred_board) | use_treasure 编排入口(treasure_interact_controller 经组合根现值回调直连);授予入口(结算段类直调);+feat_yield_adjust(专长收益调整——自编排器归位);只读投影(escrow 读与 GP 掉落合计——宿主呈现快照与 _roll_gp_drop 消费) | B4 RewardEconomy(polarity_row/grant);B1 域 DamageController(开单);BattleFxHost 引用(treasure_used 事件发射与计数器消耗);A2 minesweeper(校验回调/fx_reveal/remove_mines_in);A3 action_economy(相位闸/grant_ap/queue_ap_delta);B3 status_container(apply_effect/池读);B2 unit_container(hero_unit/heal);ConfigDB;注入 emit Callable、宿主 fatal Callable(battle_result 状态机唯一写点留宿主)、宿主 attune 读投影(_attune_safe_seen)、encounter 账本引用;TreasureItems 经宿主 setter 转发注入(库存注入时序在宿主构造之后) | 无(零随机零 await——授予期零随机口径,roll 值授予前已定) |
| **damage_controller**(B1 域·伤害单据管线) | 伤害单据唯一兑现管线(单据九栏目声明→分量掷点/克制修正/伤害下限归一→明细行发射;双方同规);`damage_taken` 唯一发射口(_emit_hero_damage_taken)+唯一消费族(_apply_hero_damage_effects)——敌方攻击/地形/危险格/错误封印直伤四路径统一发射,board_flat_hero 结账前统一发射 | 兑付入口(编排侧开单)+ fx 揭示回调注入位;+hero_atk()/static hits_of()/emit_hero_damage_taken 公开化(_emit 去下划线非并存——编排器与段类消费);实测类变量 13(facts 构造注入) | B2 单位实例(单据目标传入);B1 注入(EffectPort 端口引用——发射经端口;EffectContext 引用束与编排回调收归宿主侧 BattleFxHost 域,业务类零引擎直连) | 无 |
| **BattleRefreshFlow**(`core/battle_refresh_flow.gd`,B1 域·换图刷新流程件,已建件) | 弃图刷新流程唯一承载:刷新可用性门(相位/每轮上限/储备门槛)、折后价单点(_effective_refresh_cost——专长行声明序第一命中、floor 取整、can 与执行同源)、换图执行序(扣储备→used+1→旧资源退役→旧板退役→装新板→board_refreshed 成功事实登记) | can_refresh/execute 两公开入口(构造=实例束注入 8 键——facts/db/action_economy/fx_host/reward_economy/minesweeper/install_cb/grant_seg;类变量 8);调用路径呈现层→编排器两公开方法单行转发→RoundCycle.refresh_flow() 取件→本件;grant_seg 段间引用直接交接(编排器 _rounds._grant_seg 跨两层链式取段消灭);A3 request_refresh 收折后价入参 | 编排器(宿主,B1 域内唯一调用方约束同族适用);A3 action_economy(折价扣费与拒绝判据);A2 装板壳(装板与退役);RewardGrant(apply_board_refreshed 成功事实消费);feats 折价行经 ConfigDB;static_scan S7_B1_SET 已增补本件(判据口径不变) | 无(零随机零 await) |
| **BoardInstaller**(`core/board_installer.gd`,B1 域·装板域件,2026-10-29 尾波清偿批设计闭合——实现批建件,细则见「战斗编排域拆件定稿」节轴丙) | 棋盘安装与变异布置域唯一承载(自 battle_orchestrator 收编装板族六函数):生成受理、全局作用域变异决议、资源摇号资格域组装(roll_board 恰一次)、region 变异决议与连通片布置;BoardGenerator 实例随件自建自接线(宿主成员退役)——宿主侧装板后状态复位(封印/完美计数清零、板序推进、board_changed 发射、秘宝叠加层退役)留宿主薄壳,时序逐字保持 | `install(during_round) -> bool` 单入口;构造=引用束注入(rng/db/minesweeper/reward_economy/encounter 只读引用+宿主 fatal Callable——防 _init max-parameters 新键) | A1 board_generator(件内持有);A2 install_board;B4 roll_board/board_skew 写;A4 变异决议行集经 ConfigDB;board_gen/variant_place 两封闭流随件、流名与开流时点零漂移 | 无(零 await——装板全同步) |
| **RoundCycle**(`core/round_cycle.gd`,B1 域·轮循环驱动件,已建件) | 轮循环驱动域唯一承载:结算五段段件持有与驱动、重入门闸、开轮摇号、踩雷账 | 引用束注入(编排器 20 键封闭束,解包自用七键后原束分发五段;持有 BattleRefreshFlow);段驱动入口由宿主接线调用;公开方法 4(refresh_flow() 读出口);实测类变量 15(贴顶禁再增) | B1 宿主;settlement/ 五段段类;A3 相位读 | 无 |
| **BattleFacts**(`core/battle_facts.gd`,B1 域·战斗事实件,已建件) | 战斗事实域唯一承载:受理事实(encounter)、轮 facts 缓冲(reveals/seals/path)、封印判定账、事实溯源、同调快照读面;attune 状态两成员随件(行为函数留宿主) | 零参直构;宿主与段类经注入引用读写;实测类变量 15(贴顶)/公开方法 4(reset_round+三 mark 写口) | B1 宿主;A2/A3 只读 | 无 |
| **settlement/ 五段段类**(B1 域·结算段件) | B1 每轮结算序列的五段逻辑件(授予/增益减益/战斗/衰减/判定——细则见下「战斗域装配现状」节) | 段处理入口(B1 接线调用);构造=引用束注入(各段断言各自必需键);段类对编排器零引用(注入 Callable 亦属接线面);可直接 `new()` 单测 | B2/B3/B4 兑付面与 B1 轮 facts 缓冲(各段按职责) | 无 |
| **level_runner**(C1-lite,切片已建) | 关卡域生命周期(节点链推进、共享 B4 + HP 携带、关卡末 level_clear) | 节点推进接口;LevelTable(`level.tres`——节点 enum {battle,reward,rest,boss}、末节点必须 Boss、ConfigDB 跨表校验⑧) | B1(直构 BattleOrchestrator 接线战斗)、ConfigDB LevelTable | 无 |
| **run_structure**(C2) | 冒险/关卡序列、失败重开、**master_seed 唯一接线方** | (契约规范源:`../../gdd/systems/冒险循环/冒险结构.md`) | RngService.set_master_seed、C3/D2 | 无 |
| **difficulty**(C3) | DifficultyTable 回填、全部曲线数值调参所有权、战斗期望表(切片 monte_carlo 为工具原型) | 调参输出(经 D2 R1 解析/编排器传递,禁反向调用生成) | ConfigDB、B1 对账输出(调参约束清单)、A1 报告 C | 无 |
| **meta_progression**(C4,Alpha) | MetaTable、钥石账本、天赋树域(MetaTreeTable 12 节点 v0.8——Fork 一节点双态/row 字段退役/RULE_KEY_PARAMS 六键参数字典/彩蛋特化觉醒)+ 同调位槽位判定(树点亮数单源)。**存档内元进度数据读写原语归 MetaStateStore——已落地:本类纯转发读已退役(消费方直连 meta_state),业务规则与写口编排留本类;hero_override 内联锻造加值与树淬炼 flat;并组三组(forge_select(kind,route_id)/hero_type_axes()→{attack,armor}/tree_camp_status()→{rows,fork_groups});补 meta_state 成员装配** | 公开面 20 定稿:buy_treasure_unlock / refresh_treasure_unlocks / treasure_shop_status / forge_buy_level / forge_buy_unlock / forge_select / hero_type_axes / forge_shop_status / settle_adventure / hero_override / tree_can_buy / tree_buy / tree_toggle_fork / tree_buy_fork / tree_rules(返参数字典)/ tree_camp_status / toggle_attunement / attune_camp_status / attune_refresh_charges / buy_attune_refresh | D3、C2、ConfigDB | 无 |
### 战斗域装配现状(RunLedger 段已退役,四账去向见下「冒险域拆分(现状登记)」节)
- **结算五段**:B1 编排器的每轮结算序列拆为 `game/core/settlement/` 五个段类——GrantSegment(①授予段,含封印判定与 counter/grant 助手单点、成功刷新事实消费面 apply_board_refreshed 与轮末治疗计数兑付面)→ AuraSegment(②增益/减益段)→ CombatSegment(③战斗段)→ DecaySegment(④衰减段)→ VerdictSegment(⑤判定段,返回裁定,终局早退语义留 B1)。段序骨架冻结于 `SettlementTable.segment_order` 语义,B1 退为段序编排+接线;段类可直接 `new()` 单测(`settle_segments_test` 在役)。
### 战斗编排域拆件定稿(本节为现状登记)
- **轴甲两件**:battle_orchestrator按变化原因拆出 BattleFxHost(效果引擎宿主域)与 RewardGrant(奖励授予域)两协作件;宿主保留架构在册职责全部——战斗状态机与生命周期、轮 facts 缓冲(_facts_reveals/seals/path)、A2 三事件订阅、结算五段驱动、attune 族原位、行发射基建(_emit_row/_assert_row_source/_fatal)、呈现投影(presentation/diagnostic_snapshot 唯一出口不变)、刷新面、nest_preview_count(巢穴预告读数口——外部直读面 main.gd 与 treasure_effects_test 随批点名冻结)。拆件动机与残留债口径见待办清单「gdstyle 存量债全量清偿」条目。
- **轴丙一件(2026-10-29 尾波清偿批设计闭合——实现批建件)**:battle_orchestrator 装板域六函数(`_install_new_board`/`_resolve_global_variant`/`_place_variants`/`_place_variants_apply`/`_variants_fringe`/`_adjacent_to_packs`,约 140 行;全仓仅宿主三调用点——begin_battle/中途刷新受理/player_request_refresh,实查零外部调用)按变化原因拆出 BoardInstaller,职责语句「棋盘安装与变异布置域」(生成→全局变异决议→装板受理→资源摇号→变异布置;单一变化原因=棋盘设置顺序与变异布置算法)。形态两修正(实测裁,与初案「逐字搬运」之差):①宿主复位块不随件——`_install_new_board` 体内五处计数清零、板序推进、board_changed 发射与秘宝叠加层退役留宿主薄壳 `_install_and_reset`,三调用点改调薄壳、时序逐字;②`_board_generator` 全文件仅装板族使用——实例随件自建自接线(fatal 经注入 Callable),宿主成员退役(类变量 38→37,禁无效成员义务)。encounter(23 处跨宿主)与 _reward_economy(18 处)确认留宿主、经引用束注入。文件长键(1035/1000)随本件迁出归绿;attune 族与 begin_battle 受理段序禁动不变。
- **轴丁两件(2026-10-29 零豁免裁设计闭合——实现批建件)**:RoundCycle(轮循环驱动件)与 BattleFacts(战斗事实件)见功能层表两行——宿主 38(轴丙落地后 37)→15,S13 豁免第三条注销;宿主保留面改登记为「战斗状态机与生命周期、行发射基建(_emit_row/_assert_row_source/_fatal)、呈现投影(两快照出口签名不变)、刷新面、nest_preview_count」;attune 族处置由下一条改判;treasure_inventory 注入改址追认——原「外部赋值路径零改址」句失效,level_runner 经 RewardGrant 出口直注;成员级对账以需求卡片为卡内契约(开工 T0 复核)。
- **轴戊两件(已建件)**:①**效果端口件 EffectPort**(`core/effect_port.gd`)——职责语句「战斗业务类唯一的效果发射出口:收事件与载荷、交效果引擎结算、取回命中批」;实测形态=六 Callable 路由、bind_port 组装期转注、闭面四键 biz_fact(dig_streak/hp_ratio/rider_tier_bonus/rider_tier_crit——charge_layers 键退役后宿主应答 null)、未绑零扰动缺省;`DamageController`/`ActionEconomy` 各持端口引用(同一实例、构造注入),`EffectEngine`/`EffectContext` 引用与计数器账面(value_counter 层值读后清零)收归宿主侧(BattleFxHost 域),业务类零引擎直连、零账面触碰;facts 取值束(dig_streak/rider_tier_*)与入站回调束(reveal/ac)随发射契约归宿主侧注入面;端口**单实现零接口抽象**(无基类无多态——GDScript 无 interface,本案不需要);端口与宿主经注入 Callable 束协作、零宿主对象引用(跨件单向接线条款适用,禁双向强引用)。②**掷格件 RewardRoller**(`core/reward_roller.gd`)——职责语句「装配期格面资源摇号:池行装配、区间行 roll、权重合成(偏运硬币/board_skew 两路投影)」;实测形态=roll 批束四键(mapping/power_roll/clear_identity/clear_power)、选行算子 `_weighted_pick_row` 返表行、fatal 经构造 Callable 链;受理账面(grant/身份查询/快照/retire/polarity_row)留 `RewardEconomy`,衔接面=格→奖励身份映射(掷格件产出、受理件接收);掷格件经注入 RngServiceCore 取号(roll_board 流 scope=每次 install 批不变、取号序零漂移)。行为零变化红线沿本节「验收红线」全款:命中序=声明序、明细行协议词表零触碰、`_ineffective_row` 载荷 deferred 自描述键观测面不回退(甲案在案)、EV/MC 锚同种子逐位零漂移——round_flow 逐位回放 134 检查为主证(红线成立);`RewardEconomy` 实测类变量 11;S7 B1 域文件集与 S8 await 文件集已含两新件。
- **跨件单向接线**:RewardGrant 持 BattleFxHost 引用(treasure_used 事件发射与计数器消耗);BattleFxHost 对授予面走 bind 时注入的兑付 Callable(揭示队列 flush 回调)——禁双向强引用(两件均 RefCounted,环引用随每战新建泄漏)。行发射经 bind 时注入的 emit Callable(协议面单点留宿主——CurioDeck bind_handlers 同款先例,禁件回调宿主私有族);轮 facts 缓冲留宿主、fx 件经注入取值;_seal_consumed 留宿主(与 A2 seal_judged 订阅同域);fatal 经注入 Callable 转宿主 _fatal(battle_result 状态机唯一写点不变)。注入 Callable 亦属接线面、同单向纪律——禁为省参数改持对象引用。授予件对账本零强引用:战斗期消费经 `battle_hooks` 三键回调束(attune_consume/vision_add/negative_blast,bind_battle 缺键 assert),宿主侧三单点记账(RunLedger 类已退役,直持对象引用无处可指)。
- **战斗域读面(RunLedger 退役后)**:账本读经编排器 `_journal()`/`_attune_loadout()` 现值派生(GainJournal/AttuneLoadout,enc 派生、无成员接收);冒险桶经 `enc["adv_bucket"]` 下传 `battle_fx_host.mount_buckets`;战末 bonus 四轴经 `GainJournal.gain_bonus` 汇入。
- **settlement/ 段类随动(2026-11-04 迭代51卡② 换代)**:段类构造=引用束注入(编排器 20 键封闭束分发——各段断言各自必需键),段类对编排器零引用(跨件单向接线,注入 Callable 亦属接线面)。四个授予段 helper 归位:_feat_yield_adjust→RewardGrant(feat_yield_adjust)、_harmful_variants/_harmful_adjacent→minesweeper_rules(harmful_variants/harmful_adjacent)、_hits_of→damage_controller(static hits_of);_pool_effective/perfect_sweep_earned 留宿主(段类经编排器公开读投影消费)。
- **attune 族处置**:attune 状态两成员归 BattleFacts、行为函数留宿主——AttuneLoadout 充能单源化语义领地不破,外部三面消费点零改址;RewardGrant 经宿主 attune 读投影取 _attune_safe_seen 形态不变。
- **呈现零变化**:两快照出口签名与载荷逐字不变,内部改读两件只读投影(快照本体不读揭示态——揭示态投影消费方=宿主 A2 订阅旁路判定);treasure_interact_controller 三触点分家——use_treasure 经组合根现值回调改注 RewardGrant(每战新建编排器,禁一次性直引防悬挂),minesweeper 读与 attune_convert_state 仍走宿主取值回调(双取值面);nest_preview_count 直读面零变化。
- **验收红线**:游戏行为零变化(RNG 消费序零漂移——fx_reveal 封闭流随件迁移取号序不变、信号顺序契约冻结、明细行协议词表零触碰、同种子逐位一致);battle_orchestrator 头注发射点指址随拆件改写(词表不动,指址须真)。
- **S7/S8 登记**:RewardGrant 调 B4 API,两新件一并登记进 S7 B1 域文件集与 S8 结算路径 await 文件集(static_scan.sh 两清单独立、同步更新——防 await 面漏闸);轴丙 BoardInstaller 同规(调 B4 roll_board,已登记进两清单)。
### 冒险域拆分(现状登记——本节各条即现行形态)
- **应用层形态**:adventure_runner 退出上帝对象、转薄应用层(DDD 应用层形态)——只留冒险状态机、关序推进、结算受理、建件接线、效果引擎冒险桶字典持有(桶=效果引擎域数据,应用层只锚定生命周期);零领域决策。核心层表新件行(主角账三件与刷新账件/AttuneLoadout/CurioStop——见基础层与功能层登记)即本拆分目标。
- **CurioDeck 转纯池**:bind_handlers 反向回调退役;商店购买消费经注入 Callable 链至 CurioStop 兑付单点——牌库只管抽签/消耗/cap 判定/凭证计数。
- **RunLedger 退役**:四账各归其主——加算区/购买记录/钥石增量/真视记录归 GainJournal、特性集归 HeroStats(GP 钱包归 GpWallet),充能归 AttuneLoadout,冒险桶字典归应用层持有;战斗侧 encounter 注入面由单一账类引用改为多路(桶字典+实体账+配装件);ShopSession 账本引用面同步换代,商店属性商品加算改经 GainJournal 单写原语。
- **level_runner**:依赖改构造注入一次给齐(后置 setter 群与商店重绑机制退役;注入活引用禁快照——特性集共享引用语义保留);商店白盒转发属性撤除;战斗→关卡信号转发族保留(战斗实例每节点新建,关卡层做战斗域稳定面)。
- **断点片段化**:各件自写自读自己的键(关卡层交关卡域五键片段:node_id/visited_ids/aux_items/shop_shelf/shop_visit_buys),缺键回退策略随领域知识归各件;应用层唯一落盘口(收齐各件片段拼成顶层快照后写盘),写触发点不变(五处);既有 27 键逐键冻结;新增获得日记键沿扩列不升版先例——旧档缺日记回退汇总键直载并跳过比对,日记在档时由日记推导汇总、与留存值不符即报错中止(迁移正确性自检)。拼盘与读侧策略机组拆出 breakpoint_store(核心层表新行),应用层落盘口与写触发点不变——宿主经件组装快照后写盘,27 键冻结不变。
- **信号直连**:adventure_runner 六转发信号族(node_entered/choices_presented/step_row/round_started/board_changed/wallet_changed)退役;呈现层在 level_started 重取点重订阅关卡对象信号(旧实例连接随引用释放消亡);「关卡开始信号前零发射」契约写进关卡件头注;「恰好连接一次」断言用 is_connected 守卫(release 模板剥 assert);三选一信号面归 CurioStop。
- **调用直连**:不留中间人委托壳(纯转传=Middle Man 反模式,2026-10-26 用户裁定);呈现控制器经取值回调持件——回调只许捕获长活根对象,禁件方法 Callable(防悬挂);测试/探针直连件读写。
- **验收红线**:游戏行为零变化(RNG 流名 reward_pick 与消费序零漂移、信号顺序契约冻结、同种子逐位一致);gdstyle 基线禁改(类变量计数下降属改善);怪物寻址 resolve_enemy_rowset 调用面与关卡参数档装配零变化(未来关卡主题的数据缝不堵)。
| **battle_orchestrator**(B1,域文件集=编排器+`settlement/` 五段段类+其拆件:BattleFxHost / RewardGrant / DamageController / BattleRefreshFlow / BoardInstaller / RoundCycle / BattleFacts / EffectPort / RewardRoller) | `game/core/battle_orchestrator.gd` 等 | 战斗域编排唯一宿主:战斗状态机与生命周期、A2 三事件订阅、结算五段驱动(授予/增益减益/战斗/衰减/判定,段序冻结于 SettlementTable.segment_order)、行发射基建、呈现投影;**战斗域全部跨系统调用权集中本域**(静态扫描域 S7 执法,文件集清单单点=`game/tests/static_scan.sh`) | `begin_battle(encounter)`;窗口/刷新意图提交面;`level_clear()`;步骤流(输出契约);[明细行协议](../../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋) | B2/B3/B4 全调用面;A2 订阅;A1 generate;A3 代理+回发;D2 SettlementTable |
| **level_runner**(C1) | `game/core/level_runner.gd` | 关卡域生命周期:节点链推进、共享 B4、HP 携带、关卡末 level_clear;依赖构造注入一次给齐 | 节点推进接口;LevelTable(ConfigDB 跨表校验) | B1(直构接线战斗)、ConfigDB |
| **run_structure**(C2) | — | 冒险/关卡序列、失败重开、**master_seed 唯一接线方** | (契约规范源:`../../gdd/systems/冒险循环/冒险结构.md`) | RngService.set_master_seed、C3/D2 |
| **difficulty**(C3) | — | DifficultyTable 回填、全部曲线数值调参所有权、战斗期望表 | 调参输出(经 D2 解析传递,禁反向调用生成) | ConfigDB、B1 对账输出、A1 报告 |
| **meta_progression**(C4,业务层) | `game/core/meta_progression.gd` | MetaTable、钥石账本、天赋树域、同调位槽位判定(业务规则与写口编排;数据原语归 MetaStateStore) | tree_* / forge_* / attune_* / settle_adventure / hero_override 族 | MetaStateStore、ConfigDB |
### 呈现层
| 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API |
| 模块 | 文件 | 独占职责 | 对外封闭面 | 消费依赖 |
|---|---|---|---|---|
| **D1 HUD**(呈现面拆分持续换代:组合根 main.gd + 三屏壳(MenuScreen/CampScreen/AdventureScreen)+ views/ 视图单元(现行承载面见「呈现层装配与会话生命周期」节;早期 9 单元清单与 LevelStripView 已随场景换代退役)+三画面件内部数据类=宿主画面登记表(关卡地图屏 `_MapState`/战末弹层 `_EndOverlayState`/调试浮窗 `_DebugState` 各 14 字段公开数据成员,文件外零消费者,sweep 断言承载);营地面=场景根+四子界面功能窗口+Esc 浮窗+顶栏 HUD 栏〔四柜台覆盖层已退役〕) | 全部 Control/CanvasLayer(例外:BoardView 棋盘本体与 BattleStageView 站桩本体 = Control 壳 + SubViewportContainer + SubViewport + Node2D 世界〔BoardWorld/CellUnit;StageWorld/StandeeBody——B 区 slot_row 伴随件留壳层〕,探针契约面不变)、输入→意图翻译(**唯一提交路径 = settle_batch,不直调 A3**)、呈现义务状态记录(A2×6 / B2×4 / B3×4 / B4×5 / B1×3) | 视图单元接口(信号外发、setter 只读注入);组合根交接面=公开服务面 18(refresh_presentations/sync_status_entry/fatal_screen/quit_game/begin_session/start_debug_levels/check_board_hints/show_board_fx_for_node/replay_locked_click/sync_settlement_visuals/wire_battle/sync_debug_overlay/open_settings/open_save_select/status_data/gp_summary_text/seed_value/current_master_seed)+句柄读投影/设置口 12(set_treasure_ui/treasure_ui/set_bonus/bonus/set_camp_pres/camp_pres/set_debug/debug/set_tune_sim/tune_sim/set_confirm_consumer/set_confirm_consumer_slot)+on_* 处理器保私有经 Callable 束交付(束参=main_ui_build.build 31 键、main_adventure_flow.wire_adventure 2+6 键);指标例外=main.gd 公开方法 32 落 gdstyle 基线(先例=level_runner 33 在册);UiKit 三工厂(gradient_base/beams_rect/switch_toggle)公开化 | A2 compute_move_path / ActionOutcome;A3 ap/rap;B1 步骤流行协议(数据源);ConfigDB display_name | **MEDIUM**:Control / CanvasLayer / draw_* / InputEvent / RichTextLabel——对照 breaking-changes.md 4.5–4.7 条目(CanvasItem 线条无抗锯齿羽化、设备 ID 常量、RichTextLabel 图片参数两轮变化) |
| **组合根拆件**(呈现侧写口子件:curio_interact_controller / camp_shop_presenter / treasure_interact_controller) | 代组合根执行核心写调用(冒险节点后续推进/营地购买与刷新/道具使用——依赖与回调由 main 构造注入) | 各拆件的写入口(仅组合根装配面可达;视图零直连) | 构造注入的核心写引用(推进/商店/道具各域——详见各拆件头注) | 无 |
| **ui_registry**(`presentation/ui_registry.gd`,界面注册面,已建件) | 呈现层接口面唯一承载:层序合同(LAYER_CONTRACT)迁驻与合同寻址、三域句柄容器 ui_screens(9 屏壳与覆盖层句柄)/ui_battle(5 战区句柄)/ui_modals(模态句柄按域分三子容器——局内冒险域 run/菜单与跨语境域 menu〔两子容器〕/营地域 camp=既有 UiCampModals〔六句柄、两段式寻址 `modals.camp.*`〕,三子容器纯字段、两段式寻址 `modals.run.*`/`modals.menu.*`/`modals.camp.*`)+模态编排件 ModalFlow 直驻本面(自 UiModals 升格 UiRegistry 直持);层序合同营地槽=camp_scene+四子界面+camp_esc_overlay(camp_overlay 已退役);组合根经其一级寻址接线,探针挂载假设迁移为 `main.ui.<域容器>.<句柄>` | 三容器属性+flow+合同寻址口(RefCounted 门面,不挂树、零会话期引用);register_layer 迁驻注册面;四件类变量 UiRegistry 4/UiScreens 9/UiBattle 5/UiModals 3(run/menu/camp 三子容器纯字段——ui_modals 平铺面清零,分域纪律册级化);ModalFlow 随 UiRegistry 构造建立(注册面构造点先于装配) | 构造注入:三屏壳、覆盖层、战区句柄、ModalFlow(main_ui_build 交付随之改型) | 无(RefCounted) |
| **UiCampModals**(`presentation/ui_camp_modals.gd`,营地域句柄子容器,已建件) | ui_modals 营地域六句柄唯一容器(场景根/四子界面/Esc 浮窗/HUD 栏),句柄寻址两段式 `modals.camp.*`;ui_modals 平铺面已清零(分域纪律册级化) | 子容器属性+六句柄只读投影;main_ui_build 装配交付 | 构造注入(六视图句柄);ui_modals(父容器挂载) | 无(RefCounted) |
| **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 全宽窄条两呈现物:栏体色面(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) |
| **standee_vitals/standee_motion**(`views/`,站桩两状态件,已建件) | 站桩状态域拆分:身份命值(hp/atk/状态值族,12 成员)与资产演出(动作/姿态/位移演出族,11 成员);三个演出探针计数器留宿主,宿主实测类变量 10 | 构造注入;st(沿用)/mo 两实例由宿主持有;公开方法面 vitals 14/motion 14(站桩两状态件公开面换代,成员计数零增) | standee_body(宿主);battle_stage_view 写入面逐字保持 | 无(RefCounted) |
| **HudPresenter**(`presentation/hud_presenter.gd`) | 战斗屏**数据投影+文案拼装静态族**——核心状态→呈现文案的全部翻译集中于此(B/C/D 区文案与投影),静态无状态 | `refresh(…)` 全量重查装配(组合根每次动作/结算返回后调用,漏调即陈旧呈现)+只读静态投影族 | 核心读取只走 `settlement.presentation_snapshot()` 成文快照面(禁链式穿透结算内部);ConfigDB display_name | 无(RefCounted 静态) |
| **board_resolvers**(`presentation/board_resolvers.gd`) | 棋盘每格渲染数据的只读解析回调四函数(奖励徽章身份——挖掘期 Active 相位内、地形变异数字遮蔽与角标、调试加成生效值)——有态绑定件,与无态静态投影族 HudPresenter 职责互斥 | 构造注入 db、调试浮窗与结算活态取值,产出绑定 Callable 供组合根接线 | ConfigDB / DebugOverlay / 结算活态取值 Callable | 无(纯投影) |
| **main_ui_build**(`presentation/main_ui_build.gd`) | `_build_ui` 八步拆分件——组合根 UI 装配分步承接(三屏壳实例化、视图句柄接线、覆盖层装配与模态编排注入按步拆开);main 侧留同名薄委托,装配序与 z 序零改动 | 分步静态函数(成员访问经 root 显式传入);入口方法公开化(main 转发调用的入口去下划线) | 层序逻辑不在此件——唯一事实源=`ui_registry.LAYER_CONTRACT`+`ui.apply_layer_contract(root)`(已迁驻注册面) | 同 D1(Control 装配面) |
| **main_adventure_flow**(`presentation/main_adventure_flow.gd`) | main 冒险流程族——菜单开局/续玩后的冒险编排接线、节点事件分发、终局面与选路屏开合;逐字搬运,main 侧留同名薄委托(探针 `main.X` 路径零迁移) | 流程静态函数(root 显式);入口方法公开化(main 转发调用的入口去下划线) | 局态全局住 run_scope 节点——本件零成员态、零新增信号连接(连接语句随原段搬运) | 无 |
| **main_overlay_flow**(`presentation/main_overlay_flow.gd`) | main 浮层流程族——设置/状态面板/生涯页/选档界面/图例/钱包事件的开合与数据投喂;逐字搬运,main 侧留同名薄委托 | 流程静态函数(root 显式);入口方法公开化(main 转发调用的入口去下划线) | 全部开合走组合根模态编排口(`root.ui.flow.open/close`——ModalFlow 自 UiModals 升格 UiRegistry 直驻)——本件零成员态 | 同 D1 |
| **main_debug_flow**(`presentation/main_debug_flow.gd`) | main 调试流程族——调试浮窗开合与上下文同步、批量模拟入口、两层调试对局起手、诊断转储;逐字搬运,main 侧留同名薄委托 | 流程静态函数(root 显式);入口方法公开化(main 转发调用的入口去下划线) | 调试会话状态机归 debug_session.gd——本件只接线与出 UI,零成员态 | 同 D1 |
| **TilePalette**(`presentation/views/tile_palette.gd`) | 瓦片排版/装载:高墙/地面瓦片整批装载与运行时镜像合成、地板明度归一、数字墨族按板自适应判据、格坐标确定性散列与十六正交情形墙瓦片查表 | 查表族(读件持棋盘引用——棋盘视图 rebuild 经 `bind_board` 同步);装载缓存七成员持于件内(同板型零重载、换板重载时序同源) | 零绘制面、零玩法数据写入(绘制调用面留棋盘视图);纹理路径单点在件内 | Resource 纹理加载——LOW |
| **HeroSprite**(`presentation/views/hero_sprite.gd`) | 英雄精灵四态与落位装配(落位与位移演出、撞墙帧闪、挖掘弹跳、脚底锚与持握手位出口) | 由棋盘视图 `_init` 建为子件(注入世界根与壳层容器引用)——精灵本体经世界根 ensure_hero 建于 SubViewport 世界内,本件只驱动其状态 | BoardWorld 世界根;素材缺位=零精灵(棋盘视图字形「人」回退路径在役) | Node2D / create_tween(Tween 系 Node 方法——MEDIUM 域对照 breaking-changes.md) |
| **BoardWorld**(`presentation/views/board_world.gd`) | 棋盘 Node2D 世界根(SubViewport 内 CellUnit 容器):世界级去饱和材质与字体注入挂点、英雄精灵持有位;封印印记=参数化绘制子件 seal_mark_layer(CellUnit 新公开属性 `seal_on`:on 才建/off 撤/edge 重注入/fog_state 联动;棋盘侧 tex_seal 贴图槽退役——`seal_mark.png` 只余战末蜡封贴图专用) | 无 class_name——使用方经 preload 引用挂树 | BoardView 壳层(SubViewportContainer + SubViewport 结构不变) | Node2D/SubViewport——同 D1 |
| **battle_presentation**(B5,垂直切片后) | 结算动画 / 伤害数字 / 战斗舞台 | (契约规范源:`../../gdd/systems/战斗循环/战斗表现.md`——前置「战斗表现美术测试」锁双舞台陷阱) | B1 步骤流(回放脚本源)、D1 | **MEDIUM**:AnimationPlayer(String→StringName 变更)/ Tween |
| **audio**(D4,Alpha) | 音频挂点 | (挂点 = B1 明细行类型流;负反馈聚合——单结算至多一次) | B1 流水 | AudioStreamPlayer(4.7 area_mask 默认 1→0 注意) |
| **tutorial**(D5,Alpha) | 教学条目 | (口径来源:A2/A3/B3/B1 各教学条目) | 各上游 | 无 |
| **D1 HUD** | `game/presentation/main.gd`(组合根)+三屏壳+`views/` 视图单元族+`ui_registry.gd` | 全部 Control/CanvasLayer;输入→意图翻译(**唯一提交路径=settle_batch,不直调 A3**);组合根=信号接线与核心写调用唯一出口;views/ 各域呈现拆件的职责与接口归各件头注 | 组合根公开服务面与注册面句柄寻址合同(`main.ui.<域容器>.<句柄>`)归 main.gd / ui_registry.gd 头注承载 | A2 compute_move_path;A3 只读;B1 步骤流;ConfigDB display_name;**MEDIUM**:Control/CanvasLayer/draw_*/InputEvent/RichTextLabel |
| **组合根拆件** | `game/presentation/*_controller.gd` / `camp_shop_presenter.gd` | 代组合根执行核心写调用(冒险节点推进/营地购买与刷新/道具使用) | 各拆件写入口(仅组合根装配面可达;视图零直连) | 构造注入的核心写引用 |
| **HudPresenter** | `game/presentation/hud_presenter.gd` | 战斗屏数据投影+文案拼装静态族(核心状态→呈现文案的全部翻译集中于此) | refresh 全量重查装配+只读静态投影族 | 只走 `settlement.presentation_snapshot()` 成文快照面(禁链式穿透) |
| **board_resolvers** | `game/presentation/board_resolvers.gd` | 棋盘每格渲染数据只读解析回调四函数(与无态静态投影族职责互斥) | 构造注入产出绑定 Callable 供组合根接线 | ConfigDB / 结算活态取值 Callable |
| **battle_presentation**(B5,垂直切片后) | — | 结算动画 / 伤害数字 / 战斗舞台 | (契约规范源:`../../gdd/systems/战斗循环/战斗表现.md`——前置「战斗表现美术测试」锁双舞台陷阱) | B1 步骤流、D1;**MEDIUM**:AnimationPlayer/Tween |
| **audio**(D4,Alpha) | — | 音频挂点 | 挂点=B1 明细行类型流(负反馈聚合——单结算至多一次) | B1 流水 |
| **tutorial**(D5,Alpha) | — | 教学条目 | 口径来源=A2/A3/B3/B1 各教学条目 | 各上游 |
**依赖单向性**:上层可消费下层,禁止反向(C3 禁调用 A1 生成、D1 不直调 A3、A2/B3/B4 零相互直调)。全项目唯一回边 = A2 事件订阅面。
**依赖单向性**:上层可消费下层,禁止反向(C3 禁调用 A1 生成、D1 不直调 A3、A2/B3/B4 零相互直调)。全项目唯一回边=A2 事件订阅面。
---
## 呈现层装配与会话生命周期
### 三屏壳与组合根句柄面
### 三屏壳与组合根
- 呈现场景拆为三屏壳:MenuScreen(开始菜单屏)/CampScreen(营地屏)/AdventureScreen(冒险屏——内含关卡地图与战斗两容器)。
- 屏成员寻址迁经界面注册面(**已建件**):探针挂载假设 `main.X` 迁移为 `main.ui.<域容器>.<句柄>`(ui_screens/ui_battle/ui_modals 三域容器);main.gd 实测类变量 15,注册面形态见「模块归属·呈现层」表 ui_registry 行;ModalFlow 实例随 UiRegistry 构造建立(注册面构造点先于装配);取证探针 shot_probe/fx_shot_probe 已改址;局态一律住 RunScope 节点,禁在屏/视图侧持有局态引用。
- **组合根拆件**=组合根构造注入、代组合根执行核心写调用的呈现侧子件(现行三件:curio_interact_controller/camp_shop_presenter/treasure_interact_controller——冒险节点后续推进、营地购买与刷新、道具使用)。视图禁直连拆件;核心写调用的可达路径仍是「视图→信号→组合根(含拆件)→核心」,依赖规则与组合根单点装配不变。成员见「模块归属·呈现层」表。
- 屏成员寻址经界面注册面(ui_registry):探针与消费方走 `main.ui.<域容器>.<句柄>` 合同寻址;句柄清单与合同细节归 ui_registry 头注承载。
- 局态一律住 RunScope 节点,禁在屏/视图侧持有局态引用。
### 视觉层装配合同
- **树序 = 登记序 = z 序**,同源派生;层序唯一事实源 = `main.gd` 的 `LAYER_CONTRACT` 常量表(kind 五类:shell=屏壳〔成员随壳不占独立树位〕/modal=模态层〔入登记册,tags 随登记〕/state=全屏态/node=非视觉节点/float=不注册 ModalStack 的最顶浮层〔任何 UI 打开都不遮挡它〕)。
- 全仓唯一落位口 = `_apply_layer_contract()`(装配尾统一排序+登记);散落 `move_child` 由冒烟 V1d 源码断言判红;**改层序只改合同表**。
- **树序=登记序=z 序**,同源派生;层序唯一事实源=`main.gd` 的 `LAYER_CONTRACT` 常量表(kind 五类:shell/modal/state/node/float)。
- 全仓唯一落位口=`_apply_layer_contract()`(装配尾统一排序+登记);散落 `move_child` 由冒烟源码断言判红;**改层序只改合同表**。
- 具体层清单不复制进本文(双写失同步禁令)——查现行层项直接读 `main.gd`。
### 会话生命周期(RunScope)
- `run_scope.gd` = 纯状态承载 Node:零信号连接、零视图构建、零核心写调用(连接只在组合根,组合根经 `run_scope.X` 读写)。
- 会话更替机制:`_begin_session()` 新建会话节点挂树、旧会话节点**整节点 `queue_free()`**——无独立收束函数,跨局字段清理靠节点释放机制(新增跨局字段零新增泄漏点);`_ready` 即建闲时会话,`run_scope` 永非 null。
- **存活性矩阵**(九阶段 × 五引用面,2026-10-17 用户裁认账阶段划分;执行判据=冒烟 V3a–V3i):
- `run_scope.gd`=纯状态承载 Node:零信号连接、零视图构建、零核心写调用(连接只在组合根,组合根经 `run_scope.X` 读写)。
- 会话更替机制:`_begin_session()` 新建会话节点挂树、旧会话节点**整节点 `queue_free()`**——无独立收束函数,跨局字段清理靠节点释放机制。
- **存活性矩阵**(九阶段 × 五引用面;执行判据=系统测试 V3a–V3i):
| 阶段 | run_scope | adventure | runner | settlement |
|---|---|---|---|---|
@@ -222,117 +167,31 @@
| ④ 结算后(胜利停点)/⑤ 选路页 | 在树 | alive | alive | both |
| ⑥ 终局面 | 在树 | alive | alive | null |
| ⑦ 回菜单收束 | 新闲时会话即刻在树(旧会话已释放——无真空窗) | null | null | null |
| ⑧ 各模态开合后(设置/生涯/营地/调试) | 闲时会话零扰动 | null | null | null |
| ⑧ 各模态开合后 | 闲时会话零扰动 | null | null | null |
| ⑨ 两连局+续玩断点 | 换全新会话在树 | alive | both | both |
- 三态记号沿用冒烟参数:**alive**=应有实例、**null**=必须缺位(实测非空判红)、**both**=按分支可空可活(如胜停点后走向选路或直接终局)。对象有值但已失效一律判「悬挂」红。
- 第五引用面=四持句柄件(`_treasure_ui`/`_bonus`/`_camp_pres`/`_modal_flow`,另有 `_row_pacer`):其会话取值 Callable 与 `run_scope` 成员**逐面同源比对**(回调悬挂=两值不等即红),会话更替零残留由矩阵每阶段顺带铁证。
- 三态记号:**alive**=应有实例、**null**=必须缺位(实测非空判红)、**both**=按分支可空可活。对象有值但已失效一律判「悬挂」红。
### 模态编排(ModalFlow/ModalStack)
- `modal_flow.gd` = 模态编排纯逻辑类(依赖仅 ModalStack + 查询 Callable,可直接 `new()` 单测):开合编排(遮挡记账单口 + 演播后排窗挂起)/棋盘输入锁谓词/角标置灰谓词三职责。
- 互斥排除语义随层登记(`register(node, tags)`):`debug_overlay=["non_locking"]`、`level_screen=["screen_state"]`,其余层零标签。
- `modal_flow.gd`=模态编排纯逻辑类:开合编排(遮挡记账单口+演播后排窗挂起)/棋盘输入锁谓词/角标置灰谓词三职责;可直接 `new()` 单测。
- 互斥排除语义随层登记(`register(node, tags)`);其余层零标签。
---
## 测试与质量域
> 定位:本节为测试域(本地闸、探针、静态扫描等横切关注点)的**事实源**。全量验证=合回后远端 CI(唯一强制全量验证点,post-merge 钩子推送触发、按改动域分流);开发期义务=`process-conventions.md` §4 改完即测(改完即测自己改过的面、读数在案),判定严密性即全仓质量下限。第三方测试框架(gdUnit4)经 2026-09-12 评估不引入,现行测试载体=本节自制闸体系。
> 定位:本节只写测试域的架构事实。测试工具手册与各闸判定口径的事实源=`game/tests/README.md`;闸清单与改动域划分的唯一出处=`tools/run_all_gates.sh`(脚本即清单);测试怎么写、怎么算合格=`design/engineering/test-conventions.md`;静态扫描域 S 系规则的规则全集与动机=`game/tests/README.md`「Static Scan」节。全量验证=合回后远端 CI;开发期义务=改完即测(process-conventions §4)。
### 模块归属
| 模块 | 载体 | 独占职责 | 对外暴露 | 判定口径 |
|---|---|---|---|---|
| **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` | 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 判定(见下表) |
### 改动域划分(2026-10-17 用户裁「代码批只跑代码闸、文档批只跑文档闸」)
| 域 | 闸 | 本批改动落在这些路径即属该域 |
|---|---|---|
| **代码域** | run_smoke、run_system_test、run_all、probe_gate、render_gate、static_scan | **唯一触发=`game/` 下非 `.md` 的行为载体**(代码、测试脚本、探针、场景、数据表、素材)。`game/` 内的 `.md`(README、生成清单)归文档域——2026-10-26 用户裁:判据按文件性质,不按路径前缀。2026-10-18 用户裁「跨领域执法禁绝」后,`tools/`、`artwork/` 两前缀不把改动拉进代码域 |
| **文档域** | gdd_lint、doc_link_audit、doc_term_audit、流水线闸 | `game/` 内的 `.md` 与 `game/` 外的一切路径:`design/`、根规则文件(`AGENTS.md`/`roadmap.md`/`待办清单.md`/`体验验证清单.md`)、`tools/` 工具脚本、`.zcode/` 配置、`sim-data/`、`reports/` |
| **两域都含** | repo_structure_audit(熵增闸:顶层目录/根文件/图像白名单/退役路径/design 子目录五查——文档批新增或挪动 `.md` 同样会绊它;纯文本扫描秒级) | — |
口径三条:**域划分的唯一出处是 `tools/run_all_gates.sh`**(`--code`/`--docs`/不带参数=全量),本表是它的现状登记、不是第二份清单;改闸件本身或一次改动跨两域 → 跑全量档(§6.2 共享件例外);开发期手动档按本批改动域选 `--code`/`--docs`,合回后 CI 恒跑全量档。代码域跑前先过**素材齐盘预检**——`game/assets/art/**` 与一切 `*.import` 边车都不入 git(2026-09-29 裁定),worktree 冷检出必空盘,空盘态冒烟 V1 成片假红、渲染闸更是一段 golden 都不比。
### 车道分层执法(2026-10-31 用户裁「一切改动走 worktree」)
一切改动走独立 worktree,主工作区只作合并落点。执法全机械——判据只有 git 图与路径(豁免面 `.zcode/`、`.qoder/` 不入 git,主区直改),不含「改动大小」等语义判断。
| 执法层 | 载体 | 职责 | 判据 |
|---|---|---|---|
| **worktree-guard**(ZCode PreToolUse) | `.zcode/hooks/worktree-guard.sh`(本机件,不入 git) | agent 侧车道纪律:主工作区 Edit/Write 与主区非 merge `git commit` 拦截(豁免 `.zcode/`、`.qoder/`;worktree 内目标快速放行;合并收尾提交凭 MERGE_HEAD 豁免);`git commit --no-verify` 拦截(该旗标仅属用户);iter 分支合回一卡一提交数卡校验;规则闸 | 路径性质+分支名+MERGE_HEAD+git 图 |
验证收拢为合回后远端 CI(post-merge 钩子推送 origin master 触发 Gitea Actions,runner=Mac 本机 host 模式;按改动域分流,分流单点=tools/ci/domain_route.sh),commit 点零执法;主工作区防污染由 ZCode 层守卫承担——只约束 agent,用户手工改动不受限。子代理覆盖面(2026-10-26 冷启动评估实测):子代理的工具调用不经过 ZCode 层守卫——ZCode 层实际只覆盖主会话工具调用;残余暴露面=子代理写主工作区与蓄意 `--no-verify`(该旗标整族跳过 git 层钩子),属故意越权而非遗忘型失误,与防遗忘的设计目标正交。
### 三条判定硬闸现行状态
| 硬闸 | run_smoke | run_system_test | run_all | probe_gate | render_gate | static_scan |
|---|---|---|---|---|---|---|
| **退出码优先** | 已落实(闸①) | 已落实(闸①,口径同 run_smoke) | 已落实(套件 manifest expect 协议) | 已落实(`verdict` 函数逐段判退出码,PASS 串不作判据) | 已落实(段级退出码 + 取证双检) | 已落实(fail 聚合退出) |
| **SCRIPT ERROR 扫描** | 已落实(闸③,外部扫描——GDScript 无脚本错误陷阱 API,闸设在外部包装) | 已落实(闸③,标记清单与 run_smoke 同源) | 已落实(入口内扫子套件输出;子进程的 stdout/stderr 须先落盘——`OS.execute` 管道在本机恒空) | 已落实(逐探针段 `tools/error_scan.py`,标记与 run_smoke 同源) | 已落实(三段探针日志逐段 `tools/error_scan.py`,标记与 run_smoke 同源) | 不适用(静态域无运行时错误面) |
| **断言数下限** | 无独立下限(闸②摘要行必须在案,防「跑一半仍 PASS」;脚本错误吞断言路径由闸③兜住) | 同 run_smoke(过渡期宿主摘要行必须在案) | 已落实(`Kit.finish` 零断言返回专用退出码 2——与「断言失败=1」区分,防致命类套件的 expect 协议放过) | 已落实(退出码 0 但无该探针签名标记亦判红——防静默退出) | 不适用(golden 容差 + 取证存在性判定) | 不适用(逐规则命中判定;另有扫描面为空即判红的零命中自检) |
背景事实(run_smoke 头注在案):GDScript 运行时脚本错误不改进程退出码、不计入冒烟失败数、且静默中断所在函数剩余断言——实证:「309 项 0 失败」实为断言未跑全。这是 SCRIPT ERROR 扫描必须存在于判定层的原因。
### 静态扫描域 S1–S13 承接清单
静态扫描域规则(2026-09-12 立,沿革见 git commit log;下表为现行事实登记):
| # | 规则 | 状态 |
|---|---|---|
| S1 | 玩法代码禁 `randomize()`、自建 `RandomNumberGenerator`(D2 Rule 5) | **在位**(硬失败;扫描面 game/core + game/presentation)。非产品码三目录(`game/tests`/`game/probe`/`game/sim`)不进本闸与 S2——它们自建且**显式定种**的随机源是合法测量口径(同种子对拍改前改后读数),禁的是不定种形态;适用域分档与无机器闸的理由见 code-conventions §4 第 13 条(2026-11-03 架构审查 P1 余项裁:不扩扫描面,人工评审承载) |
| S2 | 禁全局随机函数 `randi()` / `randf()` / `randf_range()`——全局 RNG 绕开 RngService(specialist 补登) | **在位**(与 S1 同段硬失败;实例方法 `.randi()` 排除、`rng-seed-exempt` 豁免注记在案) |
| S3 | core/ 禁 ConfigDB 外 data 表加载(`load(` / `preload(` / `ResourceLoader.load`;schema class_name 直引豁免)(D2 Rule 2) | **在位**(warning 级——class_name 直引误报需人工复核;路径=`game/core`;豁免 config_db.gd 自身=数值单一事实源的正主)。2026-11-03 架构审查 P1 余项裁**维持 warning 级不升硬**:升硬的前提是先有可靠的直引误报排除判据,该判据未立;本行状态即事实源,禁在别处另列 |
| S4 | core/ 禁 `extends Control/Node2D`、禁 `.tscn` preload(D2 Rule 7) | **在位**(硬失败) |
| S5 | core/ 禁裸引用 autoload 全局标识符(`ConfigDB` / `RngService` 直接写 = --script 模式 parse error)——构造注入 only(specialist 升格) | **在位**(硬失败;形态=标识符后紧跟 `.` 的成员访问,排除注释行与字符串字面量) |
| S6 | core↔core 信号连接只许方法 Callable(禁 lambda 捕获 self);发射者禁持有订阅者反向引用 | **在位**(2026-10-25 落闸:内联 lambda 禁令机器闸——同行与跨行两形态、豁免注记 `s6-signal-exempt`、盘面零存量;「发射者禁反向引用」句静态判据不成熟,归人工评审面承接、不造弱判据) |
| S7 | B1 编排唯一性:B2/B3/B4 API 符号命中文件 ⊆ B1 文件集;A2/B3/B4 零相互直调(B1 AC-43) | **在位**(2026-10-25 落闸:符号清单×B1 域文件集,族内自调用豁免、豁免注记 `s7-b1-exempt`、缺位自检;全符号形态盘面零存量) |
| S8 | 结算路径 `await` 零命中(B1 结算/编排文件集);字面量扫描 = warning 级 + 人工三要素复核(D2 AC-14 口径) | **在位**(2026-10-25 落闸:B1 域文件集 await 零命中,排除注释与字符串;盘面零存量。清单单点=`static_scan.sh` 的 `B1_SET`,S7 的豁免名单由它派生。自检两件:①清单内文件缺位即红;②**目录域反向对账**——`game/core/settlement/` 实有 `.gd` 未登记进 `B1_SET` 即红(新段类忘登记=await 面静默失效)。反向对账只覆盖 settlement/:装板/授予/效果端口/掷格四件结算码住 `game/core` 根下,纯目录域覆盖不住,根下新增仍由本行「模块归属」B1 登记面+写卡段申报承接。本行后半句「字面量扫描=warning 级+人工三要素复核」为独立子项、仍归人工面 |
域外扩充五件:**S9** 类型闸(`tools/audit_type_hints.py`——产品码无类型容器/有值返回缺类型/参数缺类型;扫描面=产品码三目录);**S10** 文件头闸(`tools/audit_file_headers.py`——头注 `##` 载体+日期/变更叙事黑名单;扫描面=产品码三目录);**S11** 代码注释禁用词闸(扫描面=`game/` 全域 `.gd`/`.tres`/`.md`/`.json`/`.sh`,词表单点取 `design/gdd/术语表.md`);**S12** 效果引擎无状态化旧件零残留闸(扫描面=`game/` 全域 `.gd`/`.tres`,旧符号与旧 id 字面清单为脚本字面值);**S13** 第三方质量规则闸(`tools/gdstyle_gate.py`,钉 gdstyle v0.4.0;扫描面=产品码三目录,存量以 `tools/gdstyle_baseline.txt` 欠账簿登记,判定=基线外新键红、同键数值恶化红)。
S11/S12 扫描面覆盖全域,故 sim/probe/tests 的术语与旧件面**在册**;其余十一闸(S1–S10、S13)扫描面止于产品码,非产品码三目录不受约束——分档理由与各目录的运行期承接通道见 code-conventions §4 第 13 条。**防空转自检现行状态**(本文「三层承载与判定原则」要求扫描面为空即判红):S1–S8 与 S11/S12/S13 各自带空面或缺位自检;**S9/S10 两件已补齐同款自检**(`tools/audit_type_hints.py` 与 `tools/audit_file_headers.py` 缺目录或扫描面零个 `.gd` 一律判红,并绿时打印扫描面件数供核)。
### 三层承载与判定原则
测试分标准三级+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/system_test.gd` | `run_system_test.py`(系统测试闸) |
| UI 探针+渲染 golden | 场景切换与观感 | `game/probe/*.tscn` 探针 | `probe_gate.sh`(场景探针闸)、`render_gate.sh`(渲染闸) |
**判定原则**:验收不能只是「跑一遍没崩」。三硬闸(退出码/脚本错误扫描/断言数下限)是判定的底线,
缺一即判定有洞;「日志里写着 PASS」不作判据。另有两类静默失效必须机制性防范——规则扫描面为空
(路径写错即恒零命中恒绿)与子进程输出丢失(本机 `OS.execute` 管道恒空),两者都以显式自检拦住。
### 依赖约束
### 测试域依赖约束
测试域禁止反向依赖呈现层具体实现。五闸及其调用的探针场景、审计脚本只允许依赖两类边界:
1. **autoload 边界**——ConfigDB / RngService 的公开面(--script 模式不加载 autoload 且不注册全局标识符,测试文件自带显式实例化夹具);
2. **场景契约**——探针场景的信号外发面与视图单元接口(信号外发、setter 只读注入),禁直引呈现层实现内部(如 UiKit 工厂内部状态、BoardView 绘制细节)。
2. **场景契约**——探针场景的信号外发面与视图单元接口(信号外发、setter 只读注入),禁直引呈现层实现内部。
测试域是所有层的消费方,进入方式限于上述两类边界;除此之外的依赖方向一律与「模块归属」节依赖单向性同规。
### 事实源声明
本节为测试域(测试载体、判定口径、静态扫描域)的事实源。新增违规案例仍须先补 static_scan 规则再修码(code-conventions 缺陷-规范对照表纪律)。
**S13 测量面豁免注销(2026-10-29 零豁免裁)**:tune_sim_controller 改 process_frame 信号常驻连接+步进状态机(每帧一局、帧相位逐点对齐,已建件);运行态收尾现行形态=run() 尾每批一次执行。
**S13 豁免第三条注销(2026-10-29 零豁免裁)**:RoundCycle/BattleFacts 两新件设计闭合(宿主实测 15,已建件,见「战斗编排域拆件定稿」节轴丁)。
**gdstyle 计数截断缺陷登记**:gdstyle v0.4.0 对文件内 inner class 的成员计数存在截断——内嵌类中带行尾注释的属性声明截断其后续成员计数(standee_body 实测 31 声明仅记 19、board_cell_unit 33 仅记 30)。基线登记值属欠估;闸机扫描与基线同源、判定口径自洽不误报,债的真值以实查为准——按真实口径设计(实数低于阈值,不依赖解析缺陷)。登记待办:向 gdstyle 上游报 issue(仍在册)。
---
## 数据流
@@ -347,7 +206,7 @@ Input(鼠标)→ D1 输入翻译 → 意图对象 → A2.settle_batch(唯
```
- 回合制下每帧只做:输入采样、呈现刷新、动效推进——**玩法状态只在意图事务内变更**(单意图帧输入收集:stop 点击终结当帧)。
- 无跨线程玩法数据流(Godot 主线程假设,D2 EC13);A1 生成若引入 WorkerThread,join 必须先于任何快照切出(既定约束)。
- 无跨线程玩法数据流(Godot 主线程假设);A1 生成若引入 WorkerThread,join 必须先于任何快照切出(既定约束)。
### 2. 事件/信号路径(系统间通信,无紧耦合)
@@ -355,29 +214,29 @@ Input(鼠标)→ D1 输入翻译 → 意图对象 → A2.settle_batch(唯
A2 ──cell_revealed / flag_judged / round_end(棋盘路径)──▶ B1(轮 facts 缓冲)
A3 ──round_end(stopped 直发,payload 同构)──────────────▶ B1
B1 ──notify_settlement_done(next_round_mode) / notify_combat_end──▶ A3
B1 ──apply_effect / apply_damage / grant / 伤害单据 submit…──▶ B3/B2/B4(唯一调用方;段内伤害序=主角先手→逐敌还手→地图账殿后〔案C 2026-10-06〕)
B1 ──apply_effect / apply_damage / grant / 伤害单据 submit…──▶ B3/B2/B4(唯一调用方)
B1 ──install_board / retire_board──▶ A2 B1 ──generate(L,D)──▶ A1
D1 ◀──呈现数据源:A2 事件流 / B1 步骤流 / A3 ap-rap / ConfigDB display_name
```
- **事件载体(已定)**:组合根直连信号接线 + B1 Waiting 相位触发(不依赖事件到达序);autoload 全局事件总线已否决(2026-09-12,沿革见 git 历史)——单线程回合制沿用组合根直连(KISS)。选型理由=可回放(架构原则 3):同步直连让调用顺序显式可见,「同种子逐位相同」才可断言;Godot 信号本身就是同步函数派发,全库不存在事件循环。事件驱动形态服务的是并发/IO 场景对外部不确定时序的反应性,与本作「顺序确定」的需求相反。
- 事件规范序:A2 发射序 = 触发格 BFS、8 邻行主序;事件序契约 `flag_judged` < `round_end` 全路径统一。
- **呈现层订阅禁令(切片缺陷②教训固化)**:呈现层永不直接订阅 排雷玩法/行动经济 的事件流,只订 战斗结算 步骤流与 `level_runner`——核心事件形态变化不得直接冲击界面。
- **事件载体(已定)**:组合根直连信号接线 + B1 Waiting 相位触发(不依赖事件到达序);autoload 全局事件总线已否决(2026-09-12,沿革见 git 历史)——单线程回合制沿用组合根直连(KISS)。选型理由=可回放(架构原则 3):同步直连让调用顺序显式可见,「同种子逐位相同」才可断言;事件驱动形态服务的是并发/IO 场景对外部不确定时序的反应性,与本作「顺序确定」的需求相反。
- 事件规范序:A2 发射序=触发格 BFS、8 邻行主序;事件序契约 `flag_judged` < `round_end` 全路径统一。
- **呈现层订阅禁令(切片缺陷教训固化)**:呈现层永不直接订阅排雷玩法/行动经济的事件流,只订战斗结算步骤流与 `level_runner`——核心事件形态变化不得直接冲击界面。
### 3. 存档/读档路径
- 存档系统已实装:格式、三槽位、断点域与迁移链的现行契约以 `design/gdd/systems/表现与支撑/存档序列化.md`(owner)为准。
- RNG 快照契约(现行仍成立):断点续玩回填 = master_seed + 各流 scope 计数器;快照切出须发生在无正在执行中的 open_stream 实例时(scope 事件边界外——B1 编排保证:切点 = board install 边界);SAVE_FORMAT_VERSION 高版本拒读。
- RNG 快照契约(约束,现行成立):断点续玩回填=master_seed + 各流 scope 计数器;快照切出须发生在无正在执行中的 open_stream 实例时(scope 事件边界外——切点=board install 边界);SAVE_FORMAT_VERSION 高版本拒读。
### 4. 打包与字体(CJK 随包发行)
CJK 字体随包发行并设为 theme 默认字体——运行时默认字体无中文字形。此项是 HUD框架 正式化的硬性前置,同时进导出 checklist 核对项。
CJK 字体随包发行并设为 theme 默认字体——运行时默认字体无中文字形。此项是 HUD 框架正式化的硬性前置,同时进导出 checklist 核对项。
### 5. 初始化顺序
```
进程启动
→ autoload 实例化:ConfigDB → RngService → SaveManager → MetaProgression(顺序固定;战斗编排器等核心类为普通类、由组合根按局实例化)
→ autoload 实例化:ConfigDB → RngService → SaveManager → MetaProgression(顺序固定;核心类为普通类、由组合根按局实例化)
→ ConfigDB._ready():load_tables 加载 + 校验(致命通道;失败 = 构建中止 + 错误清单)
→ 主场景(D1 组合根)装配视图单元 + 信号接线
→ C2 begin_run:RngService.set_master_seed()(唯一接线方;两流计数器全零前提)
@@ -386,14 +245,14 @@ CJK 字体随包发行并设为 theme 默认字体——运行时默认字体无
→ 关卡末:通关结算(钥石/GP/专长 level_clear 渠道——现行语义归 冒险结构 GDD)
```
**启动后置条件**(2026-10-17 用户裁入册;执行判据事实源在 `game/tests/system_test.gd`):
**启动后置条件**(执行判据事实源在 `game/tests/system_test.gd`):
1. ConfigDB 就绪且表已载(`is_ready` + hero 表在座)。
2. 系统设置已落引擎态——Master 音量 mute/volume_db = settings_store 现值;减动效开关 = `UiKit.motion_scale`。
3. 启动态菜单可见(boot 后自树顶向下第一个可见内容层 = 开始层——菜单被冒险屏盖住即红)。
4. 续玩钮可见态恒等于断点在档态(双向:无断点隐藏/有断点可见/清断点回隐;boot 路径与刷新单口同源)。
5. 闲时会话已在树(`run_scope` 非空、非冒险会话、在树——永非 null 契约的启动侧证明)。
6. 调试浮窗 boot 不自动开(菜单优先,2026-10-16 用户令)+ 调试入口全局浮层角标 boot 可见。
2. 系统设置已落引擎态——Master 音量 mute/volume_db=settings_store 现值;减动效开关=`UiKit.motion_scale`。
3. 启动态菜单可见(boot 后自树顶向下第一个可见内容层=开始层)。
4. 续玩钮可见态恒等于断点在档态(双向)。
5. 闲时会话已在树(`run_scope` 永非 null 契约的启动侧证明)。
6. 调试浮窗 boot 不自动开(菜单优先)+调试入口全局浮层角标 boot 可见。
---
@@ -404,46 +263,28 @@ CJK 字体随包发行并设为 theme 默认字体——运行时默认字体无
| 边界 | 契约要点 | 调用方必须遵守的不变式 | 模块保证 |
|---|---|---|---|
| D1 → A2 | 意图经 `settle_batch(intents)` 提交;不直调 A3 | 单意图帧收集;stop 经批次委托 | 语义序仲裁 + voided 零副作用 |
| A2 → A3 | 挖/插/拔 = 先棋盘面判定后 `try_spend`;A2 不可自行扣减 | 相位判定两侧一致(同一矩阵) | 原子事务(余量+扣减+仲裁一体) |
| A2 → 下游 | 只外发三事件,payload 字段封闭(digit 恒真透传 / reward_eligible / variant_ids) | 增删事件 = 接口漂移 | 发射面封闭 + 规范序 |
| B1 → B2/B3/B4 | 全调用面集中 B1;调用序钉死(按摇定技能行兑现→apply_damage→commit_action;先兑付后衰减;段内伤害序=主角先手→逐敌还手→地图账殿后〔案C 2026-10-06〕) | 禁第二调用方(静态扫描域:API 符号命中文件 ⊆ B1 文件集) | 恰一次编排纪律(roll_board/decay/清理全序) |
| B1 ↔ A3 | 编排期代理 `request_refresh`(玩家意图,禁 RAP 满自动刷);回发 next_round_mode/battle_over | stopped/ap_exhausted 未刷新回发 new_board = 违约 fail-fast | 刷新受理契约(同 Waiting 多次合法) |
| A2 → A3 | 挖/插/拔=先棋盘面判定后 `try_spend`;A2 不可自行扣减 | 相位判定两侧一致(同一矩阵) | 原子事务(余量+扣减+仲裁一体) |
| A2 → 下游 | 只外发三事件,payload 字段封闭(digit 恒真透传 / reward_eligible / variant_ids) | 增删事件=接口漂移 | 发射面封闭 + 规范序 |
| B1 → B2/B3/B4 | 全调用面集中 B1;调用序钉死(按摇定技能行兑现→apply_damage→commit_action;先兑付后衰减;段内伤害序=主角先手→逐敌还手→地图账殿后) | 禁第二调用方(静态扫描域 S7:API 符号命中文件 ⊆ B1 文件集) | 恰一次编排纪律(roll_board/decay/清理全序) |
| B1 ↔ A3 | 编排期代理 `request_refresh`(玩家意图,禁 RAP 满自动刷);回发 next_round_mode/battle_over | stopped/ap_exhausted 未刷新回发 new_board=违约 fail-fast | 刷新受理契约(同 Waiting 多次合法) |
| 各系统 → D2 | ConfigDB 类型化属性单点读取 | 禁绕过 ConfigDB load 表、禁缓存副本、禁玩法数值字面量、禁 randomize()/自建 RNG | 校验双通道 + fail-fast 致命 |
| A1 → A2 | Board 产物 class_name 数据契约 | install 四不变式校验(violation = 拒绝 + 五类诊断枚举) | 生成期合法性闸(R₀≠S / clearable / min_safe) |
| A1 → A2 | Board 产物 class_name 数据契约 | install 四不变式校验(violation=拒绝 + 五类诊断枚举) | 生成期合法性闸(R₀≠S / clearable / min_safe) |
| A1 ↔ B1 | attempt 边界非 scope 事件;actuals p10 为对账输入(非 target 参数) | 编排不把重试当取号 | 生成期指标集输出(实测可推理率 / 实际雷数与安全格数 / 重试次数 / 降级与非法产物标记) |
| B4 → B3 | buff 类经 B1 调 apply_effect(B4 零跨系统读、零直接接口) | 满层拒绝 = 零兑付 + 明细行(无折算) | 授予时数值快照(抗热重载) |
| B1 → D1 | [步骤流行协议](../../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)(基础行型+扩展行两层封闭 + 公共字段 type/source_class/origin_cell/round_index/display_name/value/highlight? + 特有字段 target/effect_id/pool_value) | 无来源行 = 断言失败;呈现侧禁按行序重演 B2 状态机 | 可回放步骤流(同种子逐行相同) |
| B4 → B3 | buff 类经 B1 调 apply_effect(B4 零跨系统读、零直接接口) | 满层拒绝=零兑付 + 明细行(无折算) | 授予时数值快照(抗热重载) |
| B1 → D1 | [步骤流行协议](../../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)(基础行型+扩展行两层封闭 + 公共字段 type/source_class/origin_cell/round_index/display_name/value/highlight? + 特有字段 target/effect_id/pool_value) | 无来源行=断言失败;呈现侧禁按行序重演 B2 状态机 | 可回放步骤流(同种子逐行相同) |
**引擎类型核验注记**:接口使用的引擎类型 = Resource / .tres(D2 全表)、RefCounted(容器类)、Signal/Callable(事件载体)、Control/CanvasLayer/Drawing(D1)、SceneTree headless 协议(测试)——均属 4.x 基线稳定面;4.7 Control 域增量(offset_transform_* 等)已在 breaking-changes.md 登记,D1 采纳前对照核实。
**引擎类型核验注记**:接口使用的引擎类型=Resource / .tres(D2 全表)、RefCounted(容器类)、Signal/Callable(事件载体)、Control/CanvasLayer/Drawing(D1)、SceneTree headless 协议(测试)——均属 4.x 基线稳定面;4.7 Control 域增量已在 breaking-changes.md 登记,D1 采纳前对照核实。
---
## 架构原则
1. **核心层纯逻辑、headless 完整可跑**——核心层/功能层玩法逻辑零 Control/场景引用、零 UI 依赖;`--headless --script` 可跑完整战斗(D2 Rule 7 工程债;切片 88 冒烟实证)。输入是意图对象、输出是事件流/步骤流,D1 与输入设备皆可替换。
1. **核心层纯逻辑、headless 完整可跑**——核心层/功能层玩法逻辑零 Control/场景引用、零 UI 依赖;`--headless --script` 可跑完整战斗。输入是意图对象、输出是事件流/步骤流,D1 与输入设备皆可替换。
2. **单一事实源与零字面量**——玩法数值只存在于 `data/tables/*.tres`,经 ConfigDB 类型化单点读取;代码零玩法数值字面量;参数禁止跨系统复制(D2 Rule 1/2)。
3. **封闭 RNG 与可回放**——玩法代码零随机;一切随机经 RngService 具名封闭流(增流走 P1 评审);同种子 + 同输入序列 ⇒ 事件流/步骤流逐位相同(结算禁 await,B1 承重约束)。
4. **编排唯一性与事件面封闭**——战斗域跨系统调用权集中 B1(静态扫描承载);A2 三事件面封闭即负空间;每格受理、每雷转化、每次推进、每次衰减各恰一次。
5. **fail-fast + 零特判**——数据错误一律致命中止(错误数值上线比崩溃更伤);API 非法 = 拒绝 + 诊断不静默;变异零运行时分支(行为差异全部由生成端约束保证)。
6. **依赖单向 + 组合根模式**——层间单向依赖、唯一回边 = A2 事件订阅面;核心写调用唯一发生在组合根;禁上帝对象(切片 1100 行 main.gd 反面案例 → 9 单元拆分)。
7. **呈现层契约成文**——面向呈现的信号载荷必须有成文 schema;模态覆盖层必须有"打开时遮挡物隐藏"场景级断言;场景级 UI 探针测试为标配(切片缺陷 ①②④⑤ 教训沉淀)。
8. **系统无状态**——效果引擎这类系统零运行时可变状态:数据由宿主对象持有并随宿主生灭,状态按参数传入系统,系统只做判定(立项判据=2026-10-19 用户会话裁定)。系统实例不持计数器——计数器住桶(round/battle/level/adventure 四桶,EffectContext 引用束),桶住宿主。
5. **fail-fast + 零特判**——数据错误一律致命中止(错误数值上线比崩溃更伤);API 非法=拒绝 + 诊断不静默;变异零运行时分支(行为差异全部由生成端约束保证)。
6. **依赖单向 + 组合根模式**——层间单向依赖、唯一回边=A2 事件订阅面;核心写调用唯一发生在组合根;禁上帝对象。
7. **呈现层契约成文**——面向呈现的信号载荷必须有成文 schema;模态覆盖层必须有"打开时遮挡物隐藏"场景级断言;场景级 UI 探针测试为标配(切片缺陷教训沉淀)。
8. **系统无状态**——效果引擎这类系统零运行时可变状态:数据由宿主对象持有并随宿主生灭,状态按参数传入系统,系统只做判定。系统实例不持计数器——计数器住桶(round/battle/level/adventure 四桶,EffectContext 引用束),桶住宿主。
9. **停点权威在核心层**——玩家决策停点(选路、三选一、商店、恢复这类)的推进守卫住核心层状态机:停点未兑付,推进口一律拒绝(零自动推进契约);呈现层模态只做开合与输入锁——阻塞是两层结构,核心守卫为权威、模态为体验;停点态是核心状态、随断点序列化,headless 跑批与真实局同一套规则,中断续玩恢复到同一停点。
---
## 未决问题
| ID | Summary | Priority | Resolution Path |
|----|---------|----------|-----------------|
| QQ-04 | 轮内刷新数值连锁(清图支配性条款⑤ / 真人刷图行为获取期望)重算 | Medium | playtest 实测 |
| QQ-05 | 战斗末可选类"j==0 补发 1 件"兜底是否采纳(切片遗留待办) | Medium | playtest + B4 GDD 修订批 |
| QQ-06 | 可选类退出格子池后 buff 频率被动上升(44%→59%)的难度对冲杠杆(敌 HP / 权重 / 幅度) | High | C3 调参 + 切片数据复测 |
| QQ-07 | 标错惩罚(A2 OQ1:候选敌方伏击+1;hit=false 无去重的不对称裁决) | Medium | 原型小补丁实测会话(可与 v3 终验合并) |
| QQ-08 | B5 双舞台陷阱(战斗发生在棋盘上还是独立场景) | Low(B5 前) | 战斗表现美术测试后裁决 |
| QQ-09 | C3 消费 B1 对账输出的事实依赖回写 `design/gdd/02-系统清单.md` | Low | C3 GDD 定稿时 |
| QQ-10 | `.claude/docs/director-gates.md` 缺失(TD-ARCHITECTURE/LP-FEASIBILITY 判据无载体) | Low | 补建 gate 定义文件或维持 solo/lean 模式约定 |
| QQ-11 | A1 生成性能姿态(F2c 常数 C 定稿;切片实证 10×10/12×12 同步生成可接受,WorkerThread 留扩展) | Medium | A1 GDD 修订批 |
| QQ-12 | 零伤害死局跨字段/跨表护栏落点(B2 OQ9;推荐 D2 跨表校验——切片 LevelTable 校验⑧先例) | Medium | D2 校验设计批 |
| QQ-13 | A1 EC5 运行期熔断(连续降级确定性回退已知好种子) | Low | A1 GDD 修订批 |
| QQ-14 | A5 探测显影接口 / D3 存档格式(RNG 快照契约已在案,格式归 Alpha) | Low | 秘宝域正式化 / D3 Alpha 设计批 |
@@ -5160,7 +5160,7 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<rect data-graph-role="structural-frame" data-composition-frame-kind="region" data-composition-frame-id="1" data-composition-frame-label="浮层与对话框(组合根挂载)" x="90" y="390" width="1060" height="106" rx="12" class="c-region" stroke-width="1"/>
<rect data-graph-role="structural-frame" data-composition-frame-kind="region" data-composition-frame-id="2" data-composition-frame-label="逻辑层 core/(21 件·名称清单)" x="66" y="524" width="1226" height="222" rx="12" class="c-region" stroke-width="1"/>
<rect data-graph-role="structural-frame" data-composition-frame-kind="region" data-composition-frame-id="2" data-composition-frame-label="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" x="66" y="524" width="1226" height="222" rx="12" class="c-region" stroke-width="1"/>
<!-- Connection paths (before components for correct z-order) -->
<path data-edge-from="main" data-edge-to="stage" data-edge-key="0" data-edge-id="main-stage" data-composition-points="566,132;566,196;125,196;125,260" d="M 566 132 L 566 188 Q 566 196 558 196 L 133 196 Q 125 196 125 204 L 125 260" class="a-default" stroke-width="1.5" marker-end="url(#arrowhead)"/>
@@ -5375,8 +5375,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="fine" x="876" y="468" class="t-frontend" font-size="7" text-anchor="middle">confirm_dialog.gd</text>
</g>
<g id="node-debug" data-node-id="debug" data-node-label="调试浮窗" tabindex="0" role="button" aria-label="聚焦调试浮窗,DS_DEBUG 门控, 浮层与对话框(组合根挂载)" aria-pressed="false" data-node-kind="frontend" data-node-sublabel="DS_DEBUG 门控" data-node-tag="debug_overlay.gd" data-node-context="浮层与对话框(组合根挂载)">
<title>调试浮窗 · DS_DEBUG 门控 · 浮层与对话框(组合根挂载) · debug_overlay.gd</title>
<g id="node-debug" data-node-id="debug" data-node-label="调试浮窗" tabindex="0" role="button" aria-label="聚焦调试浮窗,发布态恒隐, 浮层与对话框(组合根挂载)" aria-pressed="false" data-node-kind="frontend" data-node-sublabel="发布态恒隐" data-node-tag="debug_overlay.gd" data-node-context="浮层与对话框(组合根挂载)">
<title>调试浮窗 · 发布态恒隐 · 浮层与对话框(组合根挂载) · debug_overlay.gd</title>
<rect x="968" y="420" width="152" height="56" rx="6" class="c-mask"/>
<rect x="968" y="420" width="152" height="56" rx="6" class="c-frontend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="frontend" class="semantic-sigil s-frontend" transform="translate(974 426) scale(0.6875)">
@@ -5386,12 +5386,12 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<circle cx="6.3" cy="4.8" r=".7" class="sigil-fill"/>
</g>
<text data-node-label="" data-detail-anchor="" x="1044" y="446" class="t-primary" font-size="11" font-weight="600" text-anchor="middle">调试浮窗</text>
<text data-detail="context" x="1044" y="462" class="t-muted" font-size="9" text-anchor="middle">DS_DEBUG 门控</text>
<text data-detail="context" x="1044" y="462" class="t-muted" font-size="9" text-anchor="middle">发布态恒隐</text>
<text data-detail="fine" x="1044" y="468" class="t-frontend" font-size="7" text-anchor="middle">debug_overlay.gd</text>
</g>
<g id="node-save" data-node-id="save" data-node-label="存档管理" tabindex="0" role="button" aria-label="聚焦存档管理,save_manager.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="database" data-node-sublabel="save_manager.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>存档管理 · save_manager.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-save" data-node-id="save" data-node-label="存档管理" tabindex="0" role="button" aria-label="聚焦存档管理,save_manager.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="database" data-node-sublabel="save_manager.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>存档管理 · save_manager.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="96" y="554" width="130" height="48" rx="6" class="c-mask"/>
<rect x="96" y="554" width="130" height="48" rx="6" class="c-database" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="database" class="semantic-sigil s-database" transform="translate(102 560) scale(0.6875)">
@@ -5402,8 +5402,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="161" y="592" class="t-muted" font-size="9" text-anchor="middle">save_manager.gd</text>
</g>
<g id="node-meta" data-node-id="meta" data-node-label="元进度" tabindex="0" role="button" aria-label="聚焦元进度,meta_progression.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="database" data-node-sublabel="meta_progression.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>元进度 · meta_progression.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-meta" data-node-id="meta" data-node-label="元进度" tabindex="0" role="button" aria-label="聚焦元进度,meta_progression.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="database" data-node-sublabel="meta_progression.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>元进度 · meta_progression.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="244" y="554" width="130" height="48" rx="6" class="c-mask"/>
<rect x="244" y="554" width="130" height="48" rx="6" class="c-database" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="database" class="semantic-sigil s-database" transform="translate(250 560) scale(0.6875)">
@@ -5414,8 +5414,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="309" y="592" class="t-muted" font-size="9" text-anchor="middle">meta_progression.gd</text>
</g>
<g id="node-orch" data-node-id="orch" data-node-label="战斗编排器" tabindex="0" role="button" aria-label="聚焦战斗编排器,battle_orchestrator.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="battle_orchestrator.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>战斗编排器 · battle_orchestrator.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-orch" data-node-id="orch" data-node-label="战斗编排器" tabindex="0" role="button" aria-label="聚焦战斗编排器,battle_orchestrator.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="battle_orchestrator.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>战斗编排器 · battle_orchestrator.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="383" y="554" width="130" height="48" rx="6" class="c-mask"/>
<rect x="383" y="554" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(389 560) scale(0.6875)">
@@ -5425,8 +5425,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="448" y="592" class="t-muted" font-size="9" text-anchor="middle">battle_orchestrator.gd</text>
</g>
<g id="node-rules" data-node-id="rules" data-node-label="扫雷规则" tabindex="0" role="button" aria-label="聚焦扫雷规则,minesweeper_rules.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="minesweeper_rules.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>扫雷规则 · minesweeper_rules.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-rules" data-node-id="rules" data-node-label="扫雷规则" tabindex="0" role="button" aria-label="聚焦扫雷规则,minesweeper_rules.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="minesweeper_rules.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>扫雷规则 · minesweeper_rules.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="540" y="554" width="130" height="48" rx="6" class="c-mask"/>
<rect x="540" y="554" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(546 560) scale(0.6875)">
@@ -5436,8 +5436,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="605" y="592" class="t-muted" font-size="9" text-anchor="middle">minesweeper_rules.gd</text>
</g>
<g id="node-boardgen" data-node-id="boardgen" data-node-label="棋盘生成" tabindex="0" role="button" aria-label="聚焦棋盘生成,board_generator.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="board_generator.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>棋盘生成 · board_generator.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-boardgen" data-node-id="boardgen" data-node-label="棋盘生成" tabindex="0" role="button" aria-label="聚焦棋盘生成,board_generator.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="board_generator.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>棋盘生成 · board_generator.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="688" y="554" width="130" height="48" rx="6" class="c-mask"/>
<rect x="688" y="554" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(694 560) scale(0.6875)">
@@ -5447,8 +5447,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="753" y="592" class="t-muted" font-size="9" text-anchor="middle">board_generator.gd</text>
</g>
<g id="node-boarddata" data-node-id="boarddata" data-node-label="棋盘数据" tabindex="0" role="button" aria-label="聚焦棋盘数据,board_data.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="database" data-node-sublabel="board_data.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>棋盘数据 · board_data.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-boarddata" data-node-id="boarddata" data-node-label="棋盘数据" tabindex="0" role="button" aria-label="聚焦棋盘数据,board_data.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="database" data-node-sublabel="board_data.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>棋盘数据 · board_data.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="836" y="554" width="130" height="48" rx="6" class="c-mask"/>
<rect x="836" y="554" width="130" height="48" rx="6" class="c-database" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="database" class="semantic-sigil s-database" transform="translate(842 560) scale(0.6875)">
@@ -5459,8 +5459,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="901" y="592" class="t-muted" font-size="9" text-anchor="middle">board_data.gd</text>
</g>
<g id="node-effects" data-node-id="effects" data-node-label="效果引擎" tabindex="0" role="button" aria-label="聚焦效果引擎,effect_engine.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="effect_engine.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>效果引擎 · effect_engine.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-effects" data-node-id="effects" data-node-label="效果引擎" tabindex="0" role="button" aria-label="聚焦效果引擎,effect_engine.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="effect_engine.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>效果引擎 · effect_engine.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="984" y="554" width="130" height="48" rx="6" class="c-mask"/>
<rect x="984" y="554" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(990 560) scale(0.6875)">
@@ -5470,8 +5470,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="1049" y="592" class="t-muted" font-size="9" text-anchor="middle">effect_engine.gd</text>
</g>
<g id="node-adventure" data-node-id="adventure" data-node-label="冒险运行器" tabindex="0" role="button" aria-label="聚焦冒险运行器,adventure_runner.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="adventure_runner.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>冒险运行器 · adventure_runner.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-adventure" data-node-id="adventure" data-node-label="冒险运行器" tabindex="0" role="button" aria-label="聚焦冒险运行器,adventure_runner.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="adventure_runner.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>冒险运行器 · adventure_runner.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="1132" y="554" width="130" height="48" rx="6" class="c-mask"/>
<rect x="1132" y="554" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(1138 560) scale(0.6875)">
@@ -5481,8 +5481,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="1197" y="592" class="t-muted" font-size="9" text-anchor="middle">adventure_runner.gd</text>
</g>
<g id="node-levelrun" data-node-id="levelrun" data-node-label="关卡运行器" tabindex="0" role="button" aria-label="聚焦关卡运行器,level_runner.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="level_runner.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>关卡运行器 · level_runner.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-levelrun" data-node-id="levelrun" data-node-label="关卡运行器" tabindex="0" role="button" aria-label="聚焦关卡运行器,level_runner.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="level_runner.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>关卡运行器 · level_runner.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="96" y="616" width="130" height="48" rx="6" class="c-mask"/>
<rect x="96" y="616" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(102 622) scale(0.6875)">
@@ -5492,8 +5492,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="161" y="654" class="t-muted" font-size="9" text-anchor="middle">level_runner.gd</text>
</g>
<g id="node-dmgctl" data-node-id="dmgctl" data-node-label="伤害控制" tabindex="0" role="button" aria-label="聚焦伤害控制,damage_controller.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="damage_controller.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>伤害控制 · damage_controller.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-dmgctl" data-node-id="dmgctl" data-node-label="伤害控制" tabindex="0" role="button" aria-label="聚焦伤害控制,damage_controller.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="damage_controller.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>伤害控制 · damage_controller.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="244" y="616" width="130" height="48" rx="6" class="c-mask"/>
<rect x="244" y="616" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(250 622) scale(0.6875)">
@@ -5503,8 +5503,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="309" y="654" class="t-muted" font-size="9" text-anchor="middle">damage_controller.gd</text>
</g>
<g id="node-dgmath" data-node-id="dgmath" data-node-label="伤害公式" tabindex="0" role="button" aria-label="聚焦伤害公式,damage_math.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="damage_math.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>伤害公式 · damage_math.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-dgmath" data-node-id="dgmath" data-node-label="伤害公式" tabindex="0" role="button" aria-label="聚焦伤害公式,damage_math.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="damage_math.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>伤害公式 · damage_math.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="392" y="616" width="130" height="48" rx="6" class="c-mask"/>
<rect x="392" y="616" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(398 622) scale(0.6875)">
@@ -5514,8 +5514,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="457" y="654" class="t-muted" font-size="9" text-anchor="middle">damage_math.gd</text>
</g>
<g id="node-reward" data-node-id="reward" data-node-label="奖励经济" tabindex="0" role="button" aria-label="聚焦奖励经济,reward_economy.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="reward_economy.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>奖励经济 · reward_economy.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-reward" data-node-id="reward" data-node-label="奖励经济" tabindex="0" role="button" aria-label="聚焦奖励经济,reward_economy.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="reward_economy.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>奖励经济 · reward_economy.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="540" y="616" width="130" height="48" rx="6" class="c-mask"/>
<rect x="540" y="616" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(546 622) scale(0.6875)">
@@ -5525,8 +5525,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="605" y="654" class="t-muted" font-size="9" text-anchor="middle">reward_economy.gd</text>
</g>
<g id="node-lottery" data-node-id="lottery" data-node-label="技能抽奖" tabindex="0" role="button" aria-label="聚焦技能抽奖,skill_lottery.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="skill_lottery.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>技能抽奖 · skill_lottery.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-lottery" data-node-id="lottery" data-node-label="技能抽奖" tabindex="0" role="button" aria-label="聚焦技能抽奖,skill_lottery.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="skill_lottery.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>技能抽奖 · skill_lottery.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="688" y="616" width="130" height="48" rx="6" class="c-mask"/>
<rect x="688" y="616" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(694 622) scale(0.6875)">
@@ -5536,8 +5536,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="753" y="654" class="t-muted" font-size="9" text-anchor="middle">skill_lottery.gd</text>
</g>
<g id="node-attune" data-node-id="attune" data-node-label="同调" tabindex="0" role="button" aria-label="聚焦同调,attunement.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="attunement.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>同调 · attunement.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-attune" data-node-id="attune" data-node-label="同调" tabindex="0" role="button" aria-label="聚焦同调,attunement.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="attunement.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>同调 · attunement.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="836" y="616" width="130" height="48" rx="6" class="c-mask"/>
<rect x="836" y="616" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(842 622) scale(0.6875)">
@@ -5547,8 +5547,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="901" y="654" class="t-muted" font-size="9" text-anchor="middle">attunement.gd</text>
</g>
<g id="node-action" data-node-id="action" data-node-label="行动经济" tabindex="0" role="button" aria-label="聚焦行动经济,action_economy.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="action_economy.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>行动经济 · action_economy.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-action" data-node-id="action" data-node-label="行动经济" tabindex="0" role="button" aria-label="聚焦行动经济,action_economy.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="action_economy.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>行动经济 · action_economy.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="984" y="616" width="130" height="48" rx="6" class="c-mask"/>
<rect x="984" y="616" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(990 622) scale(0.6875)">
@@ -5558,8 +5558,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="1049" y="654" class="t-muted" font-size="9" text-anchor="middle">action_economy.gd</text>
</g>
<g id="node-feats" data-node-id="feats" data-node-label="特长引擎" tabindex="0" role="button" aria-label="聚焦特长引擎,feats_engine.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="feats_engine.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>特长引擎 · feats_engine.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-feats" data-node-id="feats" data-node-label="特长引擎" tabindex="0" role="button" aria-label="聚焦特长引擎,feats_engine.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="feats_engine.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>特长引擎 · feats_engine.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="1132" y="616" width="130" height="48" rx="6" class="c-mask"/>
<rect x="1132" y="616" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(1138 622) scale(0.6875)">
@@ -5569,8 +5569,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="1197" y="654" class="t-muted" font-size="9" text-anchor="middle">feats_engine.gd</text>
</g>
<g id="node-status" data-node-id="status" data-node-label="状态容器" tabindex="0" role="button" aria-label="聚焦状态容器,status_container.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="status_container.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>状态容器 · status_container.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-status" data-node-id="status" data-node-label="状态容器" tabindex="0" role="button" aria-label="聚焦状态容器,status_container.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="status_container.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>状态容器 · status_container.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="96" y="678" width="130" height="48" rx="6" class="c-mask"/>
<rect x="96" y="678" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(102 684) scale(0.6875)">
@@ -5580,8 +5580,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="161" y="716" class="t-muted" font-size="9" text-anchor="middle">status_container.gd</text>
</g>
<g id="node-units" data-node-id="units" data-node-label="单位容器" tabindex="0" role="button" aria-label="聚焦单位容器,unit_container.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="unit_container.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>单位容器 · unit_container.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-units" data-node-id="units" data-node-label="单位容器" tabindex="0" role="button" aria-label="聚焦单位容器,unit_container.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="unit_container.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>单位容器 · unit_container.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="244" y="678" width="130" height="48" rx="6" class="c-mask"/>
<rect x="244" y="678" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(250 684) scale(0.6875)">
@@ -5591,8 +5591,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="309" y="716" class="t-muted" font-size="9" text-anchor="middle">unit_container.gd</text>
</g>
<g id="node-treasure" data-node-id="treasure" data-node-label="秘宝数据" tabindex="0" role="button" aria-label="聚焦秘宝数据,treasure_items.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="database" data-node-sublabel="treasure_items.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>秘宝数据 · treasure_items.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-treasure" data-node-id="treasure" data-node-label="秘宝数据" tabindex="0" role="button" aria-label="聚焦秘宝数据,treasure_items.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="database" data-node-sublabel="treasure_items.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>秘宝数据 · treasure_items.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="392" y="678" width="130" height="48" rx="6" class="c-mask"/>
<rect x="392" y="678" width="130" height="48" rx="6" class="c-database" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="database" class="semantic-sigil s-database" transform="translate(398 684) scale(0.6875)">
@@ -5603,8 +5603,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="457" y="716" class="t-muted" font-size="9" text-anchor="middle">treasure_items.gd</text>
</g>
<g id="node-resolver" data-node-id="resolver" data-node-label="配置解析" tabindex="0" role="button" aria-label="聚焦配置解析,config_db_resolver.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="config_db_resolver.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>配置解析 · config_db_resolver.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-resolver" data-node-id="resolver" data-node-label="配置解析" tabindex="0" role="button" aria-label="聚焦配置解析,config_db_resolver.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="config_db_resolver.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>配置解析 · config_db_resolver.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="540" y="678" width="130" height="48" rx="6" class="c-mask"/>
<rect x="540" y="678" width="130" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(546 684) scale(0.6875)">
@@ -5614,8 +5614,8 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-detail="context" x="605" y="716" class="t-muted" font-size="9" text-anchor="middle">config_db_resolver.gd</text>
</g>
<g id="node-xvalid" data-node-id="xvalid" data-node-label="配置交叉校验" tabindex="0" role="button" aria-label="聚焦配置交叉校验,config_db_cross_validator.gd, 逻辑层 core/(21 件·名称清单)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="config_db_cross_validator.gd" data-node-context="逻辑层 core/(21 件·名称清单)">
<title>配置交叉校验 · config_db_cross_validator.gd · 逻辑层 core/(21 件·名称清单)</title>
<g id="node-xvalid" data-node-id="xvalid" data-node-label="配置交叉校验" tabindex="0" role="button" aria-label="聚焦配置交叉校验,config_db_cross_validator.gd, 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)" aria-pressed="false" data-node-kind="backend" data-node-sublabel="config_db_cross_validator.gd" data-node-context="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<title>配置交叉校验 · config_db_cross_validator.gd · 逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</title>
<rect x="688" y="678" width="170" height="48" rx="6" class="c-mask"/>
<rect x="688" y="678" width="170" height="48" rx="6" class="c-backend" stroke-width="1.5"/>
<g aria-hidden="true" data-semantic-sigil="backend" class="semantic-sigil s-backend" transform="translate(694 684) scale(0.6875)">
@@ -5660,9 +5660,9 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<text data-boundary-label="" x="98" y="413" class="t-cloud" font-size="9" font-weight="600">浮层与对话框(组合根挂载)</text>
</g>
<g data-graph-role="structural-frame-label" data-composition-frame-id="2" data-composition-frame-kind="region" data-composition-frame-label="逻辑层 core/(21 件·名称清单)">
<rect data-graph-role="structural-frame-label-mask" x="70" y="534" width="172" height="16" rx="3" class="c-mask"/>
<text data-boundary-label="" x="74" y="547" class="t-cloud" font-size="9" font-weight="600">逻辑层 core/(21 件·名称清单)</text>
<g data-graph-role="structural-frame-label" data-composition-frame-id="2" data-composition-frame-kind="region" data-composition-frame-label="逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)">
<rect data-graph-role="structural-frame-label-mask" x="70" y="534" width="339.4" height="16" rx="3" class="c-mask"/>
<text data-boundary-label="" x="74" y="547" class="t-cloud" font-size="9" font-weight="600">逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)</text>
</g>
<!-- Legend -->
@@ -5859,7 +5859,7 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<h3>组合根与建树</h3>
</div>
<ul>
<li>&bull; main.tscn 全场景仅 1 个节点,整棵界面树由 main.gd 运行时搭建(1392 行)</li>
<li>&bull; main.tscn 全场景仅 1 个节点,整棵界面树由 main.gd 运行时搭建</li>
<li>&bull; 战斗界面由 UiAssembler 拼装;商店/营地浮层即拼装于战斗根内</li>
<li>&bull; 元进度账本与存档同由组合根持有(main.gd preload)</li>
</ul>
@@ -5883,7 +5883,7 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
<h3>逻辑层与边界</h3>
</div>
<ul>
<li>&bull; core/ 21 件仅列名称,源码出处见各节点 sources 指针</li>
<li>&bull; core/ 模块与拆件全量清单以 architecture.md「模块归属」表为准,本图只画顶层导航件</li>
<li>&bull; ConfigDB/RngService 两单例经 project.godot autoload 全局供给</li>
<li>&bull; smoke/ 下 13 个测试探针场景属测试基建,不入架构主干</li>
</ul>
@@ -556,7 +556,7 @@
},
{
"kind": "region",
"label": "逻辑层 core/(21 件·名称清单)",
"label": "逻辑层 core/(模块与拆件清单=architecture.md「模块归属」表)",
"wraps": [
"save",
"meta",
@@ -671,7 +671,7 @@
"dot": "cyan",
"title": "组合根与建树",
"items": [
"main.tscn 全场景仅 1 个节点,整棵界面树由 main.gd 运行时搭建(1392 行)",
"main.tscn 全场景仅 1 个节点,整棵界面树由 main.gd 运行时搭建",
"战斗界面由 UiAssembler 拼装;商店/营地浮层即拼装于战斗根内",
"元进度账本与存档同由组合根持有(main.gd preload)"
]
@@ -689,7 +689,7 @@
"dot": "rose",
"title": "逻辑层与边界",
"items": [
"core/ 21 件仅列名称,源码出处见各节点 sources 指针",
"core/ 模块与拆件全量清单以 architecture.md「模块归属」表为准,本图只画顶层导航件",
"ConfigDB/RngService 两单例经 project.godot autoload 全局供给",
"smoke/ 下 13 个测试探针场景属测试基建,不入架构主干"
]
+1 -1
View File
@@ -37,7 +37,7 @@
### 1.6 拆分交接与封装边界
本节管拆分类改动的交接面与封装面,与 §1.1 拆分判据(职责轴)配合使用:§1.1 管怎么切,本节管切完之后件与件之间怎么往来。
1. **拆分交接定义**:拆出一个新类时,交付物必须同时写明职责边界与交接定义——新类构造时需要哪些依赖,由拆出它的类在构造它时一次交给它;新类需要回调拆出它的类的哪些方法,以 Callable 形式交付。两者齐备才算拆分合格。为什么:只切职责不定义交接,新件会伸手回原类内部拿东西,封装从拆件那天起就破了。正面先例=装板件的引用束注入(表数据、随机流、装板所需对象与出错回调一次性交付——架构文档「战斗编排域拆件定稿」节 BoardInstaller 行在册)。
1. **拆分交接定义**:拆出一个新类时,交付物必须同时写明职责边界与交接定义——新类构造时需要哪些依赖,由拆出它的类在构造它时一次交给它;新类需要回调拆出它的类的哪些方法,以 Callable 形式交付。两者齐备才算拆分合格。为什么:只切职责不定义交接,新件会伸手回原类内部拿东西,封装从拆件那天起就破了。正面先例=装板件的引用束注入(表数据、随机流、装板所需对象与出错回调一次性交付——先例本体=`game/core/board_installer.gd` 构造面,沿革 git 历史可考)。
2. **封装边界**:任何代码不得访问其他对象的下划线成员;跨对象往来只走公开方法与属性。为什么:下划线只是命名约定,语言不设防、无编译期检查——此条是封装硬纪律,违者即红。
---
+2 -2
View File
@@ -63,11 +63,11 @@
| 规则 | 内容 |
|---|---|
| 树形文档体系 | 全部设计文档构成一棵树;文档增删必须同步根索引 `design/gdd/02-系统清单.md` |
| 架构事实唯一源 | `design/engineering/architecture/architecture.md` 是架构事实的唯一权威(分层/模块归属/数据流/API 边界/测试与质量域);禁建第二份架构文档;历史决策记录只进 git commit log |
| 架构事实唯一源 | `design/engineering/architecture/architecture.md` 是架构事实的唯一权威(分层/模块归属/数据流/API 边界——只写约束面:难以改变的分层、依赖规则、边界契约与关键决策;实现现状归代码与代码头注,实现历史归 git commit log);禁建第二份架构文档;**架构文档写权限=用户专属**(2026-11-07 用户裁:agent 与技能只读,修订须用户发起) |
| 一页纸节点 | 一节、一条规则 ≤一页纸(约 ≤80 行);装不下就拆节 |
| 一系统一文档 | 不建 analysis 推演件;两三句能说清的"为什么"直接写进设计目的或规则正文 |
| 正文只写现行规则 | 禁删除线、"(作废)"、"(日期+拍板)"式注记;审计信息入对应文档附录 |
| 禁实现内容 | 系统文档与概念文档全文禁写实现细节——代码路径(`.gd`/`.py`/`.sh` 与运行时文件路径)、测试文件名、断言名、类名/方法名/信号名/常量名/调试与遥测入口名、提交号一律不出现;唯一例外=数据索引(`game/data/tables/*.tres` 与 `game/data/schemas/` 的表路径与表名、表字段名与表内枚值、存档键、封闭词表标签)。实现事实归 `architecture.md`;验收标准只写玩家或系统可观测面。本行由文档域闸的能力审计机检两个可判死的面(带后缀路径、反引号调用形态),其余靠写卡段人工核对;闸件清单与域划分的唯一出处=`tools/run_all_gates.sh` 头部 |
| 禁实现内容 | 系统文档与概念文档全文禁写实现细节——代码路径(`.gd`/`.py`/`.sh` 与运行时文件路径)、测试文件名、断言名、类名/方法名/信号名/常量名/调试与遥测入口名、提交号一律不出现;唯一例外=数据索引(`game/data/tables/*.tres` 与 `game/data/schemas/` 的表路径与表名、表字段名与表内枚值、存档键、封闭词表标签)。实现事实归代码与代码头注(2026-11-07 用户裁,架构文档只写约束面);验收标准只写玩家或系统可观测面。本行由文档域闸的能力审计机检两个可判死的面(带后缀路径、反引号调用形态),其余靠写卡段人工核对;闸件清单与域划分的唯一出处=`tools/run_all_gates.sh` 头部 |
| 正文单点维护 | 一条规则句只写在定义它的那份文档(owner 文档)——此前半句全域适用;后半句(其他文档要引用时写指针或语义概括,禁复制色值、尺寸、路径等可核对的具体值)适用域=`design/gdd/**`(GDD 设计文档;2026-11-07 用户裁收窄——美术圣经与 UX 呈现规格层为可带值参考层,不在后半句约束内)。豁免面 = 执行侧文件必须带字面值才能工作时(生图提示词里的十六进制色、脚本参数),转写不算双写,但该文件头部必须写明"冲突时以 owner 文档为准" |
| 禁写变更记录 | 文档与代码文件内一律禁设「版本历史/修订记录/变更记录/修改记录/更新记录/更新日志」节或表格,禁逐版本叙述「改了哪些条款」;沿革与改动明细只进 git commit log。版本号可作为文档现行身份标记出现在文首状态行与正文引用中,但不得再挂变更表 |
| 后置 ≠ 删除 | P1/P2 后置内容逐条列出;复活后置项 = 修订文档,禁静默加回 |
+3 -3
View File
@@ -68,7 +68,7 @@
## 6. 判定三硬闸——闸承载的构造义务
任何**新增或改写的闸承载**(闸脚本/批跑编排/探针入口)必须三闸齐备;现行各闸状态登记归架构文档「测试与质量域」节(本文不另列闸件清单):
任何**新增或改写的闸承载**(闸脚本/批跑编排/探针入口)必须三闸齐备;现行各闸状态登记归 `game/tests/README.md`(测试工具手册,本文不另列闸件清单):
1. **退出码为唯一权威判据**——输出里的 PASS 串只作附加判据不作判据主体;打印 PASS 后 `quit(1)` 必须判红。
2. **SCRIPT ERROR / Parse Error 扫描面在位**——GDScript 脚本错误不改进程退出码、不计失败数(机制事实见 §7),错误标记必须扫输出,且判据必须覆盖「跑了但半途脚本错误」态——退出码 0 但输出缺预期签名同样判红(防静默退出)。
@@ -116,7 +116,7 @@
**判据出处行锚(2026-11-05 立,自 2026-11-05 起对文件名日期不早于该日的需求卡片生效;旧卡不回头改,被编入迭代开工前由写卡段按本粒度补挂)**:对照表「需求点」列每行必须带**判据出处行锚**——该行验收判据所依据的现行文档句子,格式「`<文档相对路径>:<行号>`」。现行文档=`design/` 全域现行文档(含 `ux/` 交互规格与 art-bible 的判据行);已退役的需求卡片不是现行文档,不得充当判据出处。作用=断言预期值从规格句抄出,不从实现反推:实现行为与规格句冲突时断言红;规格缺席的行写不出锚,即停卡退回写卡段(那是需求澄清缺口,不是测试缺口)。机检=`tools/pipeline_gate.py` 判据 C5(锚格式与指向存在性,按卡文件名日期分流)+`tools/capability_audit.py` 链式锚检查(断言→套件→能力行→系统文档,见 §8.9)。
**测试函数的判据出处注记(2026-11-06 立)**:`game/tests/**` 与 `game/probe/**` 内每个**验证函数**(函数体含 `check(`/`check_quiet(`/`_problems.append(`/`push_error(` 判定调用的函数——定义行与 `_check(` 前缀不作判定计)必须带至少一条**判据出处注记**,格式「`# oracle: <design/相对路径>:<行号> <判据一句>`」——指针在前、判据摘要句在后,断言预期值的出处随断言走:断言红时读注记即达出处文档句,零查表。指针空间=`design/` 全域现行文档(行为测试锚 owner 系统文档「验收标准」句或 `ux/` 规格句;惯例类测试〔封装扫描、判定通道纪律、驱动契约〕锚规约条款行);`game/sim/` 不进扫描面(测量面直构惯例)。与链式锚的分工:链式锚(断言→套件→能力行→系统文档)不变、属义务清单层;本注记属执行证据层、写在断言文件内,非独立账本(§8.9 法则 4 不变)。写新测试函数时当场挂注记;被指规格句改行号时随批维护指针——死指针即红。局限如实声明:注记是被动文本,句内容改而行数不变时机检抓不到(与卡片判据 C5 同限)。机检=`tools/capability_audit.py` 判据出处注记道(覆盖检查+指针存在性;扫描面零文件自检红)。规格缺席写不出锚=发现规格缺口:记发现不造锚、收尾呈报,禁锚到非现行文档凑数。
**测试函数的判据出处注记(2026-11-06 立)**:`game/tests/**` 与 `game/probe/**` 内每个**验证函数**(函数体含 `check(`/`check_quiet(`/`_problems.append(`/`push_error(` 判定调用的函数——定义行与 `_check(` 前缀不作判定计)必须带至少一条**判据出处注记**,格式「`# 判据出处: <design/相对路径>:<行号> <判据一句>`」——指针在前、判据摘要句在后;锚形态两档:行号锚(默认)与节锚(`<design/相对路径>「节名」<行关键词>`)——所指文档整节重排时同批把行号锚重锚为节锚,免行号漂移(2026-11-07 架构收窄批立),断言预期值的出处随断言走:断言红时读注记即达出处文档句,零查表。指针空间=`design/` 全域现行文档(行为测试锚 owner 系统文档「验收标准」句或 `ux/` 规格句;惯例类测试〔封装扫描、判定通道纪律、驱动契约〕锚规约条款行);`game/sim/` 不进扫描面(测量面直构惯例)。与链式锚的分工:链式锚(断言→套件→能力行→系统文档)不变、属义务清单层;本注记属执行证据层、写在断言文件内,非独立账本(§8.9 法则 4 不变)。写新测试函数时当场挂注记;被指规格句改行号时随批维护指针——死指针即红。局限如实声明:注记是被动文本,句内容改而行数不变时机检抓不到(与卡片判据 C5 同限)。机检=`tools/capability_audit.py` 判据出处注记道(覆盖检查+指针存在性;扫描面零文件自检红)。规格缺席写不出锚=发现规格缺口:记发现不造锚、收尾呈报,禁锚到非现行文档凑数。
**验收档申报(2026-11-04 立,自 2026-11-05 起对文件名日期不早于该日的需求卡片生效;旧卡不回头改)**:每张需求卡片在头部信息区(与设计事实源声明同区)申报一个验收档,三档:高/中/低。判据=失败代价单维——这条改动做错了,后果随存档或结算传播的(存档写读与迁移、RNG 流、战斗结算公式与乘区、经济入账、数值表结构校验)=高;后果玩家可见但不损持久状态的(常规玩法逻辑、门控、交互逻辑、呈现数据面)=中;后果即看即知且无状态残留的(文案、纯呈现样式、布局微调)=低。一张卡触多档按最高档申报;申报由写卡段落笔,用户批卡(U0)时确认或改判。档位只调节 §8.7 审计深度——闸机与 CI 的域划分、选闸算法、全量强制档一概不因档位变化,真实入口、判据绑定面、禁削弱等验收闭合判据全部照旧。未申报档位的卡一律按高档全量审计。机检=`tools/pipeline_gate.py` 判据 C4:卡内须含「验收档:高/中/低」申报行。
@@ -152,7 +152,7 @@
2. 抽查判据绑定面:断言从真实入口进入、钉在被测改动的特有可观测提交面上,且当轮真实跑批执行过;
3. 检查测试削弱:删除断言、放宽阈值、缩小扫描面、换下层直调,逐处要卡片申报或用户批准。
4. 核对内部字段直查的受控例外:直查行是否已在对照表注明理由(该行为无公开可观测面可钉)。
5. 核对判据出处:抽查断言的预期值能否从其判据出处行锚(§8.3)的规格句独立写出——预期值只在实现里有的(同义反复,oracle 镜像实现),判红。
5. 核对判据出处:抽查断言的预期值能否从其判据出处行锚(§8.3)的规格句独立写出——预期值只在实现里有的(同义反复,判据镜像实现),判红。
**审计深度分级(2026-11-04 立)**:审计按卡内申报的验收档(§8.3)逐卡定深度;梯度只落在昂贵的判断件上,机械且堵造假向量的两件(削弱检查、例外核对)三档全保留不抽。中/低档省略的测试复跑以远端 CI 读数为执行证据——`tools/ci_check.sh <合并提交>` 的现查输出(commit+run 号+conclusion)是机器产物,run 号在远端永久可复核,审计报告照抄该行即可;照抄前先核对读数必须是「绿」——「未验」(零触发批跑闸步全跳过)与「不可判」不作证据,那种批的回补办法是等一次含代码面的合回或用户自发全量回归(2026-11-04 用户裁:回执等待件退役,证据由落盘文件改为现查读数;「不采信实现者自述已全绿」的口径不变):
+2 -3
View File
@@ -2,7 +2,7 @@
> **一句话定位**:本文定义这个项目的 **Alpha 版本包含哪些东西**——它是里程碑 M3 的完成标准,也是判断"还差什么"的对照表。
> **本文只列条目,不写内容**:每样东西是什么、规则怎么走,一律归各自的权威文档,本文只给指路链接。想知道某样东西的定义,请点链接,不要在本文里找解释。
> **状态**:**已定稿(v1.29)**——2026-10-13 用户令定稿,历次升版明细见 git 提交史。Alpha 的内容范围自此冻结:增删条目一律须经用户裁决。
> **状态**:**已定稿(v1.30)**——2026-10-13 用户令定稿,历次升版明细见 git 提交史。Alpha 的内容范围自此冻结:增删条目一律须经用户裁决。
> **成文**:2026-10-13。
## 本文怎么用
@@ -146,7 +146,7 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
| 像素美术风格与色板 | [美术圣经](../art/art-bible.md) | 在役 |
| 棋盘瓦片与格面资源图标 | [美术圣经](../art/art-bible.md) | 在役 |
| 未开格类型微光(五组色相+明度档) | [HUD框架](systems/表现与支撑/HUD框架.md) | 在役(明度档 provisional 值登记见 art-bible 的 git 提交史;观感复核已随战斗一屏样张过闸完成——用户实机目检核销,沿革查 git 提交史) |
| 棋盘瓦片与立体表现重做 | [待办清单](../../待办清单.md) | 已写卡(待编排)——设计闭合=C 方向+D 布局+三关八色冻结(art-bible v3.50/battle.md 2026-11-07/HUD框架 2026-11-07);卡片=[需求卡片](../../reports/需求卡片-棋盘瓦片与立体表现重做批-2026-11-07.md)(卡内 §16 复核通过);出口=用户实机目检棋盘立体感与素材观感达标 |
| 棋盘瓦片与立体表现重做 | [待办清单](../../待办清单.md) | 已写卡(待编排)——设计闭合=C 方向+D 布局+三关八色冻结(art-bible v3.51/battle.md 2026-11-07/HUD框架 2026-11-07);卡片=[需求卡片](../../reports/需求卡片-棋盘瓦片与立体表现重做批-2026-11-07.md)(卡内 §16 复核通过);出口=用户实机目检棋盘立体感与素材观感达标 |
| 立绘显示清晰度(原稿分辨率+三档显示) | [角色设计](../art/character-design.md) | 在役(原稿分辨率入包禁降采样、显示按三档绝对高缩放、现有形象即终稿;定值单点=art-bible §8.2(档值定值面=站桩守门测试);实机目检清晰度与同屏体量顺序登记体验验证清单 ㊻) |
| 敌人立绘(全敌种) | [角色设计](../art/character-design.md) | 在役 |
| 主角立绘三态 | [角色设计](../art/character-design.md) | 在役 |
@@ -156,7 +156,6 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
| 全局纹理过滤设为邻近采样 | [待办清单](../../待办清单.md) | 在役(default_texture_filter=0+平滑渐变件三处显式 LINEAR 白名单,翻转面全枚举闭合) |
| UI 字体(全局像素字,Fusion Pixel 单套单字重) | [美术圣经](../art/art-bible.md) §7.4 | 在役(现状即终稿——拉丁沿用自带字形不另接专件、不上第二字重;信息层级手段=字号档差+墨色深浅) |
| 战斗一屏样张(界面美术的终稿闸) | [美术圣经](../art/art-bible.md) §1.4 | 在役(样张整体过闸——用户实机目检核销,沿革查 git 提交史;目标规格与五项实装定值单点=art-bible 五张规格表。主角+三 Boss 重产依赖照旧;站桩帧动画已改排 Beta,见「六 · Alpha 不含」) |
| UI 氛围层与九宫格切片观感修 | [待办清单](../../待办清单.md) | 部分在役(光柱硬边已修=连续 alpha 渐变;未结半=九宫格切片错位——Alpha 内做最小修复:核对素材尺寸,必要时重出九宫格素材,全套素材重出排 Beta;出口=结算屏与图例屏错位消失,渲染闸+用户目检通过) |
| UI 元素细节修(滑轨填充 / 图标垫块 / 留白 / 开发件外泄) | [待办清单](../../待办清单.md) | 在役(四子项中滑轨填充注销,余三件——图标垫块明度差降档/商店+营地布局修/诊断钮迁调试浮窗——全落地) |
---
@@ -193,7 +193,7 @@ GP(显示名「GP」)是冒险内的一次性属性提升货币:战斗胜
- **停点粒度**:快照只挂选路停点(入口呈现/战斗胜利后/兑付确认后的选路态)——这是唯一可恢复停点形态。战斗进行中不写快照:中断恢复后**当场战斗从头打**,棋盘中局不入档。
- **七字段拍板面**:关卡序号 / 节点位 / 生命 / 本关加成区 / GP 钱包 / 商店已购档 / 已抽模板。附随件(续玩正确性必需):主种子 + 取数计数器(rng 快照契约回填——续玩从断点号位精确续接原随机时间线,已抽模板零重抽)、钥石累计(Boss 掉落逐杀入账的中途值,终局摘要不失真)、已到节点序(关卡画面图条重建)、秘宝库存(奖励节点已选件防静默丢失)、**奖励选择态**(三选一停点中断:已选件 / 同调位替换在途——续玩已选不丢失;候选一致性=快照现状非强求 feature,2026-10-01 用户裁决)、**件获取次数计数**(kind 维度派生账本——断点附随件与读数承载)、**令牌池余库**(bonus_deck 逐枚还原——TP0 扩列,池消耗即上限的续玩正确性承载;旧版断点缺键=满库重建防御口径,单版本周期)、**商店货架态**(当前货架三件 + 本次进店已购次数;离开店=空架合法态,货架续玩由确定性重放承载:重进店同种子同货架逐位)、**Y 刷新池余量**(attune_refresh_y——冒险周期池消耗余量随断点:续玩优先读断点、不自参数面整额重注入,堵「用完 Y 退菜单再续玩满血复活」可利用漏洞;旧断点缺键回退参数面=旧档行为零变化)。
- **中断与退出两口径分离**(2026-09-29 用户裁决):中断(关游戏)= 留断点可续;**退出冒险**(放弃流程)= 失败同型结算 + **清除断点**——退出后「继续上局」不再出现。
- **获得日记键**(gain_journal,断点 28 键):获得记录明细(三层结构归架构文档「冒险域拆分」节主角账条目——本域只登记断点面口径)随断点保存,回退策略 plain;旧档回退两态——旧断点缺日记键=汇总键直载并跳过比对(零迁移损失),日记在档=由日记推导汇总值与留存值比对,不符即报错中止(迁移正确性自检)。
- **获得日记键**(gain_journal,断点 28 键):获得记录明细(三层结构归架构文档「模块归属·核心层」GainJournal 行——本域只登记断点面口径)随断点保存,回退策略 plain;旧档回退两态——旧断点缺日记键=汇总键直载并跳过比对(零迁移损失),日记在档=由日记推导汇总值与留存值比对,不符即报错中止(迁移正确性自检)。
- **续玩**:自断点重建到最近停点(逐字段回填 + 随机流回填),停点处玩家重新选路。三结局(冒险成功/失败/放弃)一律清断点——断点只活在一个进行中的冒险上。
- 与测量面的关系:断点面只在注入存档承载的正式冒险路径生效;模拟/冒烟不注入 = 整面静默,MC 逐项比对零扰动。
+3 -7
View File
@@ -11,7 +11,7 @@
>
> **兑付频率(先看这条,它是三条渠道的共同前提)**:奇物**不是打一场战斗给一份**。关内的普通战斗节点打完了什么也不给;一关之内奇物**只兑付一次**,触发点是**关底 Boss 被击败**——而按 [冒险结构](冒险结构.md) 的定义,打掉关底 Boss 恰好就等于"通过这一关",这两件事是同一个时刻,不是一条链上的两步。
>
> 状态:**已实现**;现行为 v1.6。本文件是奇物的唯一定义文档。
> 状态:**已实现**;现行为 v1.7。本文件是奇物的唯一定义文档。
>
> 数值纪律:本文件只写概念、取值域与指路;每件给多少、效果是什么一律不进正文,事实源是 `data/tables/feats.tres` 与 `data/tables/adventure.tres`。
@@ -31,7 +31,7 @@
> **落行注记**:「每第 N 次 / 积满触发」的数据行条件写 **N−1**——效果引擎分发时 when 先于 do 读计数器现值(预评估),故「满 N 触发」落行写 N−1(例:地质学家「满 4 种」写 eq 3、封印匠「每第 3 次」写 mod_eq ref 2、双管镐「每第 2 次」写 mod_eq ref 1)。行为仍为每第 N 次准点触发,单元测试钉住;新件落行照此口径。
### 基础件表(27 件,五族 + 跨族桥 + 契约尖点)
### 基础件表(26 件,五族 + 契约尖点)
**开格系(6)**
@@ -69,11 +69,7 @@
**踩雷转化对账锚(自爆流 kill-switch,2026-10-11 用户裁)**:踩雷的净血账期望必须为负——雷伤 > 血蜜回复 + 其余转化件的血当量;对账归调参批,两轮数值迭代压不住则砍血蜜。防「见雷就踩」退化(喂雷流教训同源);主动/误踩不判定(用户裁:不预判玩家意图,拿件自选收益最高的操作)。
**跨族桥(封印系 × 踩雷系)(1)**
| 代号 | on / when / do(声明要点) | 账 |
|---|---|---|
| 破封一击 | on 挖开有封印印记的危险格:额外 +1 层(疼痛记忆同账) | 印记=标定引信、挖=引爆——推理定位的显式回报;挂棋盘事实(挖开格当时有封印印记)非玩家意图判定(扩签:被挖格当时有封印印记) |
**跨族桥退役(2026-11-07 用户裁)**:破封一击(on 挖开有封印印记的危险格:额外 +1 层)退役进[道具退役素材库](../../../content-archive/道具退役素材库.md)表五——「封印受理即揭示、封印格拒绝挖掘」口径换代后「挖开封印格」路径不存在,专长永远无法触发;其触发事实词「被挖格当时有封印印记」随件自引擎除名。跨族桥位现空缺。
**节奏系(3)**
@@ -1,7 +1,7 @@
# 地图战斗资源
> 一句话:格面资源的产出、极性字典与清图奖励封装
> 状态:已定稿;现行为 v1.6
> 状态:已定稿;现行为 v1.7
## 设计目的
@@ -108,8 +108,7 @@
| 玩家动作 | 安全格 | 危险格 |
|---|---|---|
| 开格 | **+类型**:该格资源的正向兑付(奖励) | **−类型**:该格资源的负向兑付(惩罚) |
| 封印且留到轮末判定 | **−类型**:标错,该格资源的负向兑付(惩罚) | **+类型**:标中,该格资源的正向兑付(奖励) |
| 封印格直接挖开 | 零兑付(四情况④:零惩罚零奖励——标空不判、正向作废) | **−类型**照常(四情况②:只受挖雷一层惩罚——标中不发) |
| 封印(受理即揭示、留到轮末判定——封印格拒绝挖掘,挖开封印格路径不存在) | **−类型**:标错,该格资源的负向兑付(惩罚);该格资源正向不兑付 | **+类型**:标中,该格资源的正向兑付(奖励) |
矩阵的定义归本文;把矩阵跑起来的路由(结算队列、判定基线、逐行兑付)归[战斗结算](战斗结算.md)的授予段。blast 型负向 = 警觉。矩阵只描述现行规则;被本矩阵取代的旧体系与历次结构变更(撤旗退役、伏击换装等)只进 git 提交史,不在正文复述。
@@ -147,7 +146,7 @@
### 清图奖励对象:整图封装,可配置可替换
清图奖励不属于任何一格的格面资源——它是作用域为整张板的独立奖励对象:把一张图的雷全部清完(全部安全格开完)时结算发的那一份。装板摇号时与格子资源同批判尾摇定身份;清图批兑付恰好一次;完美封印(零误标且至少一次封印标中)追加第二份同身份兑付。清图奖励只在清图批兑付:弃图刷新不触发清图事件,换来的新图不发清图奖励;刷新专长的固定 GP 收益走战斗内缓冲入账,不属清图奖励封装。
清图奖励不属于任何一格的格面资源——它是作用域为整张板的独立奖励对象:把一张图的雷全部清完(全部安全格完成——挖开或封印,口径见[排雷玩法](../排雷循环/排雷玩法.md))时结算发的那一份。装板摇号时与格子资源同批判尾摇定身份;清图批兑付恰好一次;完美封印(零误标且至少一次封印标中)追加第二份同身份兑付。清图奖励只在清图批兑付:弃图刷新不触发清图事件,换来的新图不发清图奖励;刷新专长的固定 GP 收益走战斗内缓冲入账,不属清图奖励封装。
它是可配置可替换的封装:数据面上是清图池里的一行(现行默认槽 clear_default),换行即换内容,机制零改动。按图类型差异化已留结构位——`clear_<图类型>` 命名空间预留,不同地图类型可以配不同的清图奖励;词表现行 = {dungeon, underdark, deep_underdark}(玩家侧「地图类型」),差异化内容启用随内容批。
@@ -1,7 +1,7 @@
# 战斗结算
> 一句话:轮末结算的编排与逐行兑现
> 状态:已定稿;现行为 v1.8
> 状态:已定稿;现行为 v1.9
## 设计目的
@@ -19,7 +19,7 @@
|---|---|
| 轮次编排 | 战斗开始序列、每轮五段结算、五种出口分发(继续 / 换图 / 挖掘期刷新 / 战斗结束 / 关卡结束) |
| 结算触发与段序 | 轮末自动触发一次整轮结算;五段顺序为冻结结构,改动走文档修订而非调参 |
| 结算队列与判定基线 | 挖掘期动作(开格、封印)入结算队列;封印判定以轮末在场封印为基线,逐行兑付(撤旗动作退役) |
| 结算队列与判定基线 | 挖掘期动作(开格、封印)入结算队列;封印判定以轮末在场封印为基线,逐行兑付(封印格拒绝挖掘,受理过的封印必然在基线) |
| 明细流水协议 | 结算中的每件事逐行留档并标注来源,是呈现、回放对账、数据导出共读的唯一数据源 |
| 收益对照表 | 一张效果档位对照表归一全部收益的折算口径(详见核心规则) |
| 伤害与治疗边界 | 修正池合成与封顶、溢杀丢弃、治疗封顶、死亡判定时机 |
@@ -100,7 +100,7 @@
基础 9 类写的是裸机制的账:玩家没有任何专长时,结算流水的每一行都出得了这九类。有了专长之后,效果引擎会在段内挂点插入额外的结算效果,这类效果写**扩展行**。扩展行是**一个**行型,不是每种专长效果各占一个行型——具体插的是哪种效果,由行携带的效果身份(effect_id 载荷)说明,呈现层按身份取各自的行文案。专长会不断增加、效果种类不可穷举,行型词表因此对扩展类封口:新增专长效果只新增载荷身份,不再新增行型。
扩展行与基础行共用同一套行协议:公共字段、来源归因、两层豁免、无来源即炸,全部照常生效。发射时点由段序骨架的挂点决定(时序与挂点施加面归架构文档「战斗域装配现状」节),本节只管这行是什么、带什么字段。扩展行携带效果身份(effect_id 载荷);效果身份清单与各发射点归架构文档「战斗域装配现状」与「战斗编排域拆件定稿」节登记。
扩展行与基础行共用同一套行协议:公共字段、来源归因、两层豁免、无来源即炸,全部照常生效。发射时点由段序骨架的挂点决定(时序与挂点施加面、效果身份清单与各发射点归代码文件头注承载——模块归属见架构文档「模块归属·功能层」battle_orchestrator 行),本节只管这行是什么、带什么字段。扩展行携带效果身份(effect_id 载荷)。
举证:本节为行协议约定,无计算。
@@ -127,22 +127,15 @@ percent 类(每结算、每层): E = |层数 × 行值| ÷ 100 ×
### 授予段兑付:极性路由,四类动作各归各系统资源
授予段消费结算队列。**队列与判定基线**:挖掘期玩家对格子的动作——开格、封印——都进入结算队列;结算逐批兑付,不走逐动作出栈直算。封印判定以轮末在场封印位为基线:被直接挖开的封印已随挖掘消失、不在基线(零兑付——标中 / 标空让位于挖掘本体结局,见下文封印不可逆四情况);开格事实不可撤(已开格无法回退)。过滤后按触发源分三批兑换,每格受理、每封印判定恰好一次。
授予段消费结算队列。**队列与判定基线**:挖掘期玩家对格子的动作——开格、封印——都进入结算队列;结算逐批兑付,不走逐动作出栈直算。封印判定以轮末在场封印位为基线:封印格拒绝挖掘,受理过的封印必然保留到轮末、全部在基线;开格事实不可撤(已开格无法回退)。过滤后按触发源分三批兑换,每格受理、每封印判定恰好一次。
兑付走**极性矩阵**(定义归[地图战斗资源](地图战斗资源.md)):开安全格 → 该格格面资源的正向;开危险格 → 负向;封印标中 → 该危险格资源的正向;标错封印 → 该安全格资源的负向。九型字典里每型各有一行正向、一行负向(gp 正负两向兑付 = 战斗内 GP 缓冲,见下方兑付形态表);伏击已退役,blast 型负向现况 = 警觉(敌方护甲等级 +1)。
**封印不可逆四情况(2026-09-19 用户拍板)**:封印印记是一次性承诺、移除动作退役、挖封印格放行——兑付按四情况改判:
| 情况 | 兑付 |
|---|---|
| ① 插危险格、留到轮末判定 | 标中照常(该危险格资源正向) |
| ② 插危险格、再直接挖开 | 只受挖雷一层惩罚(该危险格资源负向照常兑付;挖雷惩罚因惊动触发);标中不发(封印已随挖掘消失、不入判定基线) |
| ③ 插安全格、留到轮末判定 | 标空惩罚照常(该安全格资源负向) |
| ④ 插安全格、再直接挖开 | 零惩罚零奖励——正向兑付作废,记「奖励作废(封印格直接挖开)」归因行;标空判定同②自然缺席 |
**封印受理即揭示(2026-11-07 用户裁)**:封印受理只完成揭示与立印——墙瓦片换地板、格下身份当场可见(裁决见[排雷玩法](../排雷循环/排雷玩法.md)),兑付仍全部发生在轮末判定批,受理当场零兑付;封印格拒绝挖掘,「挖开封印格」的兑付改判路径随之不存在。
| 批 | 兑付 |
|---|---|
| 挖格批 | 开到的安全格逐格兑该格资源的正向(封印格直挖除外——四情况④零奖励);开到的危险格逐格兑该格资源的负向(封印危险格照常——四情况②) |
| 挖格批 | 开到的安全格逐格兑该格资源的正向;开到的危险格逐格兑该格资源的负向 |
| 封印判定批 | 标中则兑该危险格资源的正向,每颗雷的资格只兑一次;标空则记"封印未中"归因行,并兑该安全格资源的负向 |
| 清图批 | 清图奖励对象恰好发一次;完美封印追加第二份同身份兑付 |
@@ -162,7 +155,7 @@ percent 类(每结算、每层): E = |层数 × 行值| ÷ 100 ×
警觉(blast 负向)按个体归属:每个存活敌各发一条自己的实例(全体存活敌规则,见[增益状态](增益状态.md)〈目标规则与个体归属〉节),只活一轮、轮末随体清零;战斗段由防守方护甲等级读数消费(我打敌 = 该敌 armor_class + 它名下的 ac 型状态合计)。其余各型负向受池 clamp 与叠层上限承载。
标中资格认雷不认封印:兑付资格以危险格实例为键——封印印记无任何移除手段(移除动作已退役,重复封印被"已有封印"拒绝)、同一颗雷跨轮不重复兑付;换图后新雷是新实例,资格自然重置。同一处封印一直落在雷上,每轮都会重新上报标中,去重是本系统的职责。
标中资格认雷不认封印:兑付资格以危险格实例为键——封印印记无任何移除手段(封印格拒绝挖掘,重复封印被"已有封印"拒绝)、同一颗雷跨轮不重复兑付;换图后新雷是新实例,资格自然重置。同一处封印一直落在雷上,每轮都会重新上报标中,去重是本系统的职责。
标错的惩罚:标空封印(标了但格不是雷)= 该安全格格面资源的负向兑付(极性矩阵的"标错"分支),类型化、确定性、零额外派生。负向兑付与"封印未中"归因行同键去重,每危险格实例只触发一次;叠层已满授予被拒时记"奖励未生效"行,不静默吞掉。
@@ -325,7 +318,7 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值
1. 战斗开始序列跑完(关闸门、初始化状态池、掷骰定敌数、生成敌人、装板并预分配资源、发开轮通知 + 开轮摇号),进入第一轮挖掘期。
2. 挖掘期本系统只缓冲事实流(开格、封印入结算队列),不做任何兑付。
3. 轮末自动触发五段结算,触发与事件到达序无关。
4. 授予段按判定基线过滤(被挖开的封印不入判定),再按极性矩阵三批兑现:开安全格兑正向(封印安全格直挖 = 四情况④零奖励作废行)、开危险格 / 标错封印兑负向(封印危险格直挖照常——四情况②)、清图发清图奖励对象(完美乘数读 settlement.tres p_perfect)。
4. 授予段按轮末在场封印基线兑付(封印格拒绝挖掘,在场封印必然全部在基线),再按极性矩阵三批兑现:开安全格兑正向、开危险格 / 标错封印兑负向、清图发清图奖励对象(完美乘数读 settlement.tres p_perfect)。
5. 增益合成、主角先手加逐敌按摇定技能兑付(伤害技三乘法链 / 增益技施放行)加地图伤害账殿后(暂存单随战斗段末统一结账)、衰减恰好一次。
6. 判定段先主角后敌人:继续通知同图、清图换图续轮、胜负走战斗结束清理(秘宝背包不清)。
7. 明细流水全程落行,供呈现、回放与导出。
@@ -361,7 +354,7 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值
- 五段全序:任一轮结算的明细行按五段顺序排列。
- 触发与到达序无关:同一轮末的两条结束消息按任意先后驱动,结算步骤流逐行相同。
- 挖封印格兑付改判:有封印格直接挖开被受理——封印安全格直挖零奖励(作废归因行恰一次)、封印危险格直挖只吃挖雷惩罚;被挖开的封印不入轮末判定(零标中 / 标空兑付);开格事实不可撤。
- 封印受理即揭示零兑付:封印受理只完成揭示与立印,当场零资源兑付(无正向无负向);兑付只发生在轮末封印判定批——标中正向 / 标空负向,在场封印全部入判定基线;开格事实不可撤。
- 每事实恰一次:每格受理、每封印判定恰好一次;重复投递不改变结果。
- 极性路由正确:开安全格 / 封印标中只兑正向、开危险格 / 标错封印只兑负向;兑付效果与该格资源类型的字典行一一对应。
- 警觉按个体:每只敌各持自己的警觉实例、互不共享;blast 负向逐层直授无上限(个体化——2026-11-02 用户裁)。
@@ -1,7 +1,7 @@
# 地形变异
> 一句话:格子的非雾变异(荆棘丛/藤蔓缠绕/岩浆/烬覆/落石/矿尘)与 Boss 图整图变异(贫矿脉),挖掘中可遭遇的地貌偏离
> 状态:已定稿;现行为 v1.3
> 状态:已定稿;现行为 v1.4
## 设计目的
@@ -64,7 +64,7 @@
机制口径:
- 伤害型:挖开变异格(含连锁揭示,chain 通道同口径)在轮末结算逐行兑现扣生命——地形后果同挖雷口径,非奖励、封印格直挖不作废;危险格恒不吃变异(双层叠罚污染逐行归因,首版排除)。
- 伤害型:挖开变异格(含连锁揭示,chain 通道同口径)在轮末结算逐行兑现扣生命——地形后果同挖雷口径,非奖励(封印受理只揭示不挖开,不触发变异伤害——挖开封印格路径已删,口径见[排雷玩法](排雷玩法.md));危险格恒不吃变异(双层叠罚污染逐行归因,首版排除)。
- 信息型:已开且显示数字时该格真实数字不呈现(呈现层读取遮蔽;挖掘规划被迫降级为盲区推理)。呈现层读取不回灌规则本体——模拟试玩走查询面、不经遮蔽,读数口径不受影响。
- 直伤量为加法核小整数(effect_value ∈ [1,9],行值 provisional 归难度曲线与Boss);每格携带单类型单 id。
- 变异格未开时无预告呈现(遭遇式;视觉标识归表现层升级批)。
+20 -19
View File
@@ -1,7 +1,7 @@
# 排雷玩法
> 一句话:挖格、封印、连锁展开等扫雷规则本体
> 状态:已定稿
> 状态:已定稿(v2.0)
## 设计目的
@@ -11,13 +11,13 @@
## 系统范围
边界这样划:一个动作"碰棋盘"的部分全归这里——合不合法、执行后格子变成什么样、翻开了哪些格、封印印记立在哪;"花多少钱"归行动经济,"兑现成什么战斗效果"归战斗结算,"画成什么样"归 HUD 框架,"格子怎么生出来"归棋盘生成。本系统自己不含任何玩法数值、零随机、也不认界面:同一张板、同一份动作序列,必然得到逐位相同的结果。
边界这样划:一个动作"碰棋盘"的部分全归这里——合不合法、执行后格子变成什么样、哪些格被揭示、封印印记立在哪;"花多少钱"归行动经济,"兑现成什么战斗效果"归战斗结算,"画成什么样"归 HUD 框架,"格子怎么生出来"归棋盘生成。本系统自己不含任何玩法数值、零随机、也不认界面:同一张板、同一份动作序列,必然得到逐位相同的结果。
负责:
| 负责 | 说明 |
|---|---|
| 全部棋盘面规则 | 动作合法性(相邻、开未开、封印位)、连锁展开、数字提示、封印印记的棋盘状态、清图判定(安全格是否全部开完) |
| 全部棋盘面规则 | 动作合法性(相邻、开未开、封印位)、连锁展开、数字提示、封印印记的棋盘状态、清图判定(安全格是否全部完成——挖开或封印) |
| 开局初始展开 | 装板事务:结构前提校验、免费展开出生开阔地、小人落位 |
| 单一结算入口 | 玩家意图只从批次结算入口进,按固定语义序仲裁 |
| 三种对外事件 | 挖开格、封印判定、轮次结束——全项目的事实流源头 |
@@ -49,15 +49,15 @@
移动不花行动点:点任何已开格,小人沿已探明区一次走到位,"已探明区"包括踩过的危险格残址。点击已开格的唯一含义就是移动——本作没有经典扫雷里"双击数字格一键翻开周围"的 chord(双击快开),每一格的兑现都必须花行动点。
挖已封印的格是合法动作(2026-09-19 用户拍板):封印印记是一次性承诺、没有撤旗动作,直接挖开就是对承诺的兑现——封印印记随挖掘消失、该封印轮末不再判定;挖开封印格的兑付改判(标中不发 / 安全格零奖励等四情况)归[战斗结算](../战斗循环/战斗结算.md)。
封印与挖开同样揭示格子(2026-11-07 用户裁):封印被受理的那一刻,墙瓦片换成地板瓦片、格下身份(危险格还是安全格、安全格的数字)当场可见,封印印记叠在其上——玩家不用等轮末判定就知道封中还是封空。兑付仍然全部发生在轮末判定批,受理当场零兑付、零事件。封印格从此拒绝挖掘:挖已封印的格被拒(拒绝原因独立可辨),"封印后再挖开"这条路径不存在。封印印记没有任何撤下手段——不能被挖开、不能被撤下,随图存续到换图或战斗结束。
| 动作 | 合法对象 | 棋盘面拒绝情形 |
|---|---|---|
| 挖 | 8 向相邻、未开(有封印照挖) | 不相邻 / 已开过 |
| 挖 | 8 向相邻、未开、无封印 | 不相邻 / 已开过 / 已封印 |
| 封印 | 8 向相邻、未开、无封印 | 不相邻 / 已开过 / 已有封印 |
| 移动 | 任意已开格,且能只经已开格连通到达 | 目标未开 / 无连通路径 |
每个被拒动作都带一个可辨的拒绝原因;原因只能取自一份固定清单,清单外没有别的来源。三个来源互不混淆:棋盘面非法(上表各情形,细因彼此数据可分——"已有封印"和"不相邻"必须是两个可区分的原因)、资源不足(判定权在行动经济)、相位不对(本轮已收尾,不收动作)。玩家看到的文案归[HUD 框架](../表现与支撑/HUD框架.md)。
每个被拒动作都带一个可辨的拒绝原因;原因只能取自一份固定清单,清单外没有别的来源。三个来源互不混淆:棋盘面非法(上表各情形,细因彼此数据可分——"已有封印"(封印动作)与"已封印"(挖动作)和"不相邻"必须互可区分)、资源不足(判定权在行动经济)、相位不对(本轮已收尾,不收动作)。玩家看到的文案归[HUD 框架](../表现与支撑/HUD框架.md)。
```
一个意图进来
@@ -73,16 +73,16 @@
每个已开安全格都亮着一个数字,恒等于它周围 8 格里的雷数。这条"数字恒真"是全部推理的地基:数据层从不说谎,呈现层怎么遮掩都不改真值——任何让数字失真的实现都在禁止之列。
挖开一个数字为 0 的格(周围一圈没有雷)时,它周围未封印的安全格免费连锁全开,一路递推到没有新的 0 格为止——这就是连锁展开,玩家看到的是"坍塌显形":挖穿一层薄壳,整片松动的地面塌陷露出。连锁格只扣首格挖掘按价计的一笔,照常计入清图进度(清图 = 开完全部安全格),格面资源资格也照常——连锁开出的格与单挖的格同权,资源身份归[地图战斗资源](../战斗循环/地图战斗资源.md)、极性兑付归[战斗结算](../战斗循环/战斗结算.md),本系统不区分。
挖开一个数字为 0 的格(周围一圈没有雷)时,它周围未封印的安全格免费连锁全开,一路递推到没有新的 0 格为止——这就是连锁展开,玩家看到的是"坍塌显形":挖穿一层薄壳,整片松动的地面塌陷露出。连锁格只扣首格挖掘按价计的一笔,照常计入清图进度(清图 = 全部安全格完成,完成 = 挖开或封印),格面资源资格也照常——连锁开出的格与单挖的格同权,资源身份归[地图战斗资源](../战斗循环/地图战斗资源.md)、极性兑付归[战斗结算](../战斗循环/战斗结算.md),本系统不区分。
连锁遇到封印就停:封印格不被链开,也不给更远的格当传播跳板。封印按价计费,正是玩家亲手买下的"链阻断器"——对一片不敢确定的地,提前落一枚封印印记,塌方到它为止。
清图在每次挖完成后判定;封印、移动都不改已开格,永远不会触发清图。
清图在每次挖或封印完成后判定;移动不改格子,永远不会触发清图。
| 连锁展开的性质 | 含义 |
|---|---|
| 免费 | 只扣首格按价计的一笔,连锁翻开的格不另收费 |
| 计入清图进度 | 连锁开的格照常算进"全部安全格开完"的判定 |
| 计入清图进度 | 连锁开的格照常算进"全部安全格完成"的判定 |
| 资源资格照常 | 连锁开出的安全格与单挖的安全格同权 |
| 遇封印即止 | 封印格不入链、不作传播跳板;封印印记留在原地,轮末照常判定 |
@@ -134,9 +134,9 @@
### 封印立得住、也带不走:封印印记的生命周期与换图重置
封印印记是棋盘状态的一部分,不是背包里的实物:落下的封印印记跨轮保留、随图存续,没有数量上限,也没有任何移除手段(移除动作已退役——封印印记只能被挖开,不能被撤下)。每次轮次结束(行动点耗尽、玩家停手或清图时),本系统对当时在场的每一处封印如实上报一条判定——封印底下是雷记标中(玩家看到危险格被控制、化险为利),不是雷记标空;封印一直落在雷上,就每轮如实重复上报标中,去重是[战斗结算](../战斗循环/战斗结算.md)的事,本系统只报事实。被直接挖开的封印随挖掘消失、不在轮末在场基线里,自然不报判定;封印本身不发事件,界面反馈走调用返回值。
封印印记是棋盘状态的一部分,不是背包里的实物:落下的封印印记跨轮保留、随图存续,没有数量上限,也没有任何撤下手段——封印格拒绝挖掘(见「挖、封印、移动」节),印记只能随图更替消失。每次轮次结束(行动点耗尽、玩家停手或清图时),本系统对当时在场的每一处封印如实上报一条判定——底下是雷记标中(玩家看到危险格被控制、化险为利),不是雷记标空;封印一直落在雷上,就每轮如实重复上报标中,去重是[战斗结算](../战斗循环/战斗结算.md)的事,本系统只报事实。封印本身不发事件,界面反馈走调用返回值。
标空(封错格)的纠错路径只有一条:把它挖开(兑付改判四情况见[战斗结算](../战斗循环/战斗结算.md));不挖也可以留着它当链阻断器、当"别走这边"的地图注记。封印印记不参与清图判定,但带封印的格没挖开就照常挡着——全图封印一格不挖,永远清不了图。
标空(封错格)没有纠错路径:印记不撤不消、随图存续——封错的格从此是一块标了价的地图注记:它算完成探索(计入清图进度),但格面资源不兑付,轮末标空的负向兑付(归[战斗结算](../战斗循环/战斗结算.md))就是封错的代价;它同时仍是链阻断器和不可通行的地形。清图判定口径(2026-11-07 用户裁):清图 = 全部安全格完成,完成 = 挖开或封印——封印可以完成清图,全图不可能被"封错一格"卡死。
标空就是标错,玩家读到的是控制失败——惩罚兑付为该安全格格面资源的负向(极性矩阵,归[地图战斗资源](../战斗循环/地图战斗资源.md)与[战斗结算](../战斗循环/战斗结算.md)),本系统只报"标空"事实。
@@ -150,7 +150,7 @@
| 随战斗结束消失 | 整板退役 | 与板一同清空 |
```
封印(按价计费,界面即时反馈)→ 封印印记立在未开格上,跨轮保留
封印(按价计费,界面即时反馈)→ 封印印记立在已揭示格(地板)上,跨轮保留
每次轮次结束 → 逐封印判定,如实上报(标中 / 标空)
换图(清图或刷新)或战斗结束 → 封印印记随旧图全量消失
```
@@ -159,11 +159,11 @@
### 一帧一个意图、对外只有三种事件:批次仲裁与事件面
玩家每帧只提交一个意图,全部意图经同一个批次结算入口进入本系统——这是唯一的结算入口,保证任何情况下只有一种结算顺序。批次是整体事务:先按固定语义序重排(能清图的挖最优先、停手次之、其余殿后,同类内按提交顺序),再逐个执行;轮次结束条件第一次成立处定出赢家,其后的意图全部丢弃。丢弃不等于被拒:拒绝是本来就不合法,丢弃是来不及了——零棋盘效果、零扣费、零事件。单意图帧下正常玩不出批次竞争,这套仲裁是防御性兜底。
玩家每帧只提交一个意图,全部意图经同一个批次结算入口进入本系统——这是唯一的结算入口,保证任何情况下只有一种结算顺序。批次是整体事务:先按固定语义序重排(能清图的挖或封最优先、停手次之、其余殿后,同类内按提交顺序),再逐个执行;轮次结束条件第一次成立处定出赢家,其后的意图全部丢弃。丢弃不等于被拒:拒绝是本来就不合法,丢弃是来不及了——零棋盘效果、零扣费、零事件。单意图帧下正常玩不出批次竞争,这套仲裁是防御性兜底。
| 批次语义序 | 类别 | 说明 |
|---|---|---|
| 最先 | 能清图的挖 | 执行后图即全开的挖——防止"耗尽优先"把清图高光结算成最差结局 |
| 最先 | 能清图的挖或封 | 执行后图即全开的挖或封——防止"耗尽优先"把清图高光结算成最差结局 |
| 次之 | 停手 | 玩家主动收轮 |
| 其余 | 普通挖、封印、移动 | 同类内按提交顺序 |
@@ -179,7 +179,7 @@
```
一批意图到达
→ 按语义序重排(能清图的挖 > 停手 > 其余,同类按提交序)
→ 按语义序重排(能清图的挖或封 > 停手 > 其余,同类按提交序)
→ 逐个执行,轮次结束第一次成立处定赢家,其后意图丢弃
→ 收轮:先把封印判定逐条报完,再报轮次结束
```
@@ -193,7 +193,7 @@
| 方向 | 系统 | 接口要点 |
|---|---|---|
| 上游 | [棋盘生成](棋盘生成.md) | 棋盘产物的消费方;装板时校验结构前提(出生区无雷、数字恒真等),违例拒绝装板 |
| 上游 | [行动经济](行动经济.md) | 唯一扣费入口:本系统判棋盘面后请行动经济扣费,绝不自行扣减;挖成后上报连锁规模与清图事实;停手意图经批次入口转交 |
| 上游 | [行动经济](行动经济.md) | 唯一扣费入口:本系统判棋盘面后请行动经济扣费,绝不自行扣减;挖成或封成后上报连锁规模与清图事实;停手意图经批次入口转交 |
| 下游 | [战斗结算](../战斗循环/战斗结算.md) | 三事件的订阅方、轮末兑现方;换新图指令由它回发 |
| 下游 | [地图战斗资源](../战斗循环/地图战斗资源.md) | 本系统只上报"这格有无格面资源资格"的事实;资源内容与极性兑付不经本系统 |
| 下游 | [HUD 框架](../表现与支撑/HUD框架.md) | 呈现义务归 HUD 框架 |
@@ -203,7 +203,7 @@
最小闭环只保留跑通"装板—挖—收轮—兑现—换图"必需的规则:
1. 战斗开始装上新图:校验结构前提,违例拒绝;初始展开免费翻开出生开阔地,小人落位。
2. 玩家每帧提交一个意图:挖(判棋盘面,请行动经济按价扣费,开格加连锁并逐格上报;有封印格照挖、封印印记随挖掘消失)、封印(按价计费,封印印记入棋盘,零事件)、移动(免费,已开区一次走到位)。
2. 玩家每帧提交一个意图:挖(判棋盘面,请行动经济按价扣费,开格加连锁并逐格上报;封印格拒绝挖掘)、封印(按价计费,受理即揭示——墙变地板、身份可见,印记立其上;零事件)、移动(免费,已开区一次走到位)。
3. 被拒动作带可辨原因、状态不变;批次内轮次结束首次成立处定赢家,其后意图丢弃。
4. 收轮:先报完在场封印的逐条判定,再报轮次结束(停手路径由行动经济直发)。
5. 战斗结算订阅事件流逐行兑现;清图或刷新换图时按它回发的指令装新图,旧板封印印记全量失效,回到第 1 步。
@@ -219,7 +219,7 @@
## 调参表
本系统无自有参数:价目与每轮点数见[行动经济](行动经济.md)的调参表,棋盘形态见[棋盘生成](棋盘生成.md)。连锁免费、移动免费、无 chord、批次语义序、三种事件封闭都是冻结设计而非参数——改动走本文修订流程,不走调参。
本系统无自有参数:价目与每轮点数见[行动经济](行动经济.md)的调参表,棋盘形态见[棋盘生成](棋盘生成.md)。连锁免费、移动免费、无 chord、封印受理即揭示、清图口径(挖开或封印算完成)、批次语义序、三种事件封闭都是冻结设计而非参数——改动走本文修订流程,不走调参。
## 验收标准
@@ -228,7 +228,8 @@
- 数字恒真:任意已开安全格的数字恒等于其 8 向相邻雷数;出生区无雷、数字恒真、初始展开不吞全图三条结构前提任一不满足时,装板被拒绝。
- 初始展开恰等:装板后翻开的格集合恰为"出生 3×3 加连通开阔地"——不止 3×3,也不外扩一圈;全程免费、恒无格面资源(封锁格)、计入清图进度;跨轮不重放。
- 连锁免费且遇封印即止:一次触发连锁翻开任意多格,扣费恒为首格按价计的一笔;封印格不被链开、不作传播跳板;连锁格计入清图进度、资源资格照常。
- 挖封印格放行:有封印格直接挖开被受理——封印印记随挖掘消失、该封印轮末不判;重复封印仍被"已有封印"拒绝;兑付改判四情况(标中不发 / 安全格零奖励)归战斗结算。
- 封印受理即揭示、挖封印格被拒:封印被受理后该格进入已揭示态——身份(危险/安全与数字)当场可查、封印印记叠在其上、受理当场零兑付零事件;挖已封印的格被拒(拒绝原因独立可辨、零副作用);重复封印仍被"已有封印"拒绝。
- 清图口径:清图 = 全部安全格完成——挖开或封印都算完成;封到安全格计入清图进度,封印可以完成清图。
- 已引爆危险格可通行:挖雷后轮次不结束,小人可站、可经已开区穿行续挖;危险格不计清图、不可再挖再封印。
- 移动合法且预览同源:目标未开或无已开区连通路径时拒绝;路径预览与实际执行逐格相同;移动免费、零事件。
- 批次仲裁与到达序无关:同一批意图按任意顺序提交,结束路径与结算结果一致;赢家定出后其余意图丢弃——零棋盘效果、零扣费、零事件,且丢弃区别于拒绝。
@@ -1,7 +1,7 @@
# 行动经济
> 一句话:每轮行动点怎么花、停手与刷新的代价
> 状态:已定稿;现行为 v1.5
> 状态:已定稿;现行为 v1.6
## 设计目的
@@ -55,7 +55,7 @@
| 动作 | 花费 | 说明 |
|---|---|---|
| 挖开格 | 标准价(见 action_economy.tres) | 只能挖小人 8 向相邻的格子;若触发连锁展开(flood fill 式整片翻开),连锁翻开的部分免费,且计入清图进度 |
| 封印 | 标准价(见 action_economy.tres) | 同样限相邻格。封印印记只是棋盘上的标记,不是背包里的实物,想用多少有多少——它的稀缺性完全由"要花一次标准价行动点"承载。封印印记是一次性承诺:落子就没有移除动作,纠错 = 直接挖开该格 |
| 封印 | 标准价(见 action_economy.tres) | 同样限相邻格。封印印记只是棋盘上的标记,不是背包里的实物,想用多少有多少——它的稀缺性完全由"要花一次标准价行动点"承载。封印印记是一次性承诺:落子就没有移除动作;封印受理即揭示、封印格拒绝挖掘(口径见[排雷玩法](排雷玩法.md))——封错的代价 = 轮末标空负向兑付,纠错路径不存在 |
不花钱的有两件事:移动小人(点已翻开的格子走过去,仅限已探明区域)和连锁展开。这两项免费是冻结设计,服务"边缘探索不心疼"的氛围,不是经济杠杆。
@@ -56,7 +56,7 @@
第二条边界是反面清单:局内资源不入存档。行动点、储备行动点、道具库存、生命、GP 全部单局清零——各归各的系统管,没有局内数值需要持久化。若未来某局内资源要开放跨局携带(如储备行动点跨战斗携带),须修订其池规则并向本系统登记持久化归属,禁止静默超域。
**唯一豁免 = `breakpoint` 断点域**:断点是冒险进行中的瞬态快照,不是跨局携带——里面的生命/GP/道具库存只在「续玩上局」时还原进局内,冒险收束(冒险成功/失败/放弃)即整域清除,永远不会渗进永久进度。反面清单的管辖对象因此收窄为 `meta` 永久进度域(冒烟域界扫描收窄到 meta 域);断点域自身的字段口径归[冒险结构](../冒险循环/冒险结构.md)登记。断点顶层键(28 键)的登记单源=冒险应用层的断点键表(键→回退策略;现行契约见架构文档「冒险域拆分」节);写读已换代为各件自交片段——主角账/停点件/关卡件各自写读自己的键(缺键回退策略随领域知识归各件),冒险应用层是唯一拼盘落盘口(收齐各件片段拼成顶层快照后写盘,写触发点不变)。**关卡完成停点的同调未用行留存**:`aux_items` 键在此停点形态含同调未用行(「同调行始终留在 treasure_inventory」与「键值形态逐位不变」在关卡完成停点互斥,采行留存;玩家可见 C 区投影零变化,24 停点充能读数逐位一致)。
**唯一豁免 = `breakpoint` 断点域**:断点是冒险进行中的瞬态快照,不是跨局携带——里面的生命/GP/道具库存只在「续玩上局」时还原进局内,冒险收束(冒险成功/失败/放弃)即整域清除,永远不会渗进永久进度。反面清单的管辖对象因此收窄为 `meta` 永久进度域(冒烟域界扫描收窄到 meta 域);断点域自身的字段口径归[冒险结构](../冒险循环/冒险结构.md)登记。断点顶层键(28 键)的登记单源=冒险应用层的断点键表(键→回退策略;现行契约见架构文档「模块归属·核心层」breakpoint_store 行);写读已换代为各件自交片段——主角账/停点件/关卡件各自写读自己的键(缺键回退策略随领域知识归各件),冒险应用层是唯一拼盘落盘口(收齐各件片段拼成顶层快照后写盘,写触发点不变)。**关卡完成停点的同调未用行留存**:`aux_items` 键在此停点形态含同调未用行(「同调行始终留在 treasure_inventory」与「键值形态逐位不变」在关卡完成停点互斥,采行留存;玩家可见 C 区投影零变化,24 停点充能读数逐位一致)。
**三存档位**:存档是 3 个独立的 JSON 文件(三个独立存档文件,槽文件名只经存档系统单点派生,槽 1 禁复用旧单档名),字典键一律字符串。写盘目标=当前激活槽的文件;启动(boot)不读任何槽文件——启动态=无激活态,不读写;激活态由选档界面的「新档」或「进档」建立。数据模型(现行 = v7;示例值全为零值默认态):
@@ -55,7 +55,7 @@ HUD 框架定稿时已为教学留好两个承载位,本系统的内容将来
|---|---|---|
| 行动点价目 | [行动经济](../排雷循环/行动经济.md) | 挖 / 封印各花多少、移动与连锁免费 |
| 储备池清零提示 | [行动经济](../排雷循环/行动经济.md) | 必须说清"储备行动点在战斗结束时清零",防止"攒的钱凭空没了"的误读 |
| 封印是一次性承诺的首遇提示 | [排雷玩法](../排雷循环/排雷玩法.md)(封印不可逆批 2026-09-19) | 经典扫雷玩家会习惯性右键拔除标记——本作封印印记不能拔,落子就是承诺;想改主意就左键直接挖开(安全格零奖励、危险格受挖掘惩罚)。每场冒险提示一次,非模态 |
| 封印是一次性承诺的首遇提示 | [排雷玩法](../排雷循环/排雷玩法.md)(封印受理即揭示口径 2026-11-07) | 经典扫雷玩家会习惯性右键拔除标记——本作封印印记不能拔,落子就是承诺:受理即揭示,封中还是封空当场可见,没有反悔路径(封错的代价=轮末标空负向兑付,印记随图存续)。每场冒险提示一次,非模态 |
| chord 不存在的首遇提示 | [HUD 框架](HUD框架.md)(承接排雷玩法的呈现义务) | 经典扫雷玩家会习惯性双击数字格——本作没有 chord,点数字格 = 移动小人。每场冒险提示一次,非模态 |
| 挖非相邻格的首次提示 | [HUD 框架](HUD框架.md) | 挖与封印都限小人 8 向相邻的格子,第一次越界操作时提示 |
| 增益叠层节奏 | [增益状态](../战斗循环/增益状态.md) | "同一轮拿满优于跨轮慢慢叠"——增益有结算次数衰减,教学要传达这个节奏 |
+4 -4
View File
@@ -1,7 +1,7 @@
# 术语表
> 全库专用术语的唯一对照表(2026-10-11 用户令建立)。规矩:新术语先登记本表、再在文档使用;退役词见文末对照表,文档正文一律使用现行词。文档中遇到看不懂的词,查本表。用词纪律(禁用词、替换值、豁免范围)见「用语规范」节。
> 状态:**现行 v1.27**——2026-10-11 建立;历次升版明细见 git 提交史。
> 状态:**现行 v1.28**——2026-10-11 建立;历次升版明细见 git 提交史。
## 格与棋盘
@@ -12,7 +12,7 @@
| 安全格 | 没有惩罚的格子——挖开安全并获得其上信息与收益 |
| 整图 | 单张棋盘(的整个范围)——原「整图」退役(2026-10-11 用户裁)。例:整图叠加层、整图作用域 |
| 叠加层(整图叠加层) | 秘宝产生的信息的自持容器:按效果分类的格集,随图消亡、不回写棋盘(见[秘宝](systems/跨局循环/秘宝.md)) |
| 封印 / 控制 | 封印动作的机制名/玩家名:封印印记落在危险格上为「标中」(危险格被控制、化险为利),封错为「标空」(原「旗 / 控制」,2026-10-18 用户裁:「旗」概念不存在——动作=封印、印记=封印印记),板面单字字形=「封」 |
| 封印 / 控制 | 封印动作的机制名/玩家名:受理即揭示——墙瓦片换地板、格下身份当场可见(2026-11-07 用户裁),封印格拒绝挖掘、印记随图存续;封印印记落在危险格上为「标中」(危险格被控制、化险为利),封错为「标空」(原「旗 / 控制」,2026-10-18 用户裁:「旗」概念不存在——动作=封印、印记=封印印记),板面单字字形=「封」 |
| 真视 | 直接告诉玩家某范围内哪些格是危险格的效果(秘宝承重契约:一次最多 3×3,见[秘宝](systems/跨局循环/秘宝.md)核心规则) |
| 类型微光 | 棋盘上按格子类型显示的提示光效(视野参数控制其显示范围) |
| 换图 / 弃图刷新 | 清完一图进入下一图 / 花储备放弃当前图重开一张(代价见[行动经济](systems/排雷循环/行动经济.md)) |
@@ -94,7 +94,7 @@
| 术语 | 定义 |
|---|---|
| 效果端口件 | 战斗业务类(伤害管线/行动经济)唯一的效果发射出口:收事件与载荷、交效果引擎结算、取回命中批——业务类零引擎直连(机制归属见架构文档「战斗编排域拆件定稿」节轴戊;实现类 EffectPort) |
| 效果端口件 | 战斗业务类(伤害管线/行动经济)唯一的效果发射出口:收事件与载荷、交效果引擎结算、取回命中批——业务类零引擎直连(机制归属见架构文档「模块归属·功能层」battle_orchestrator 行;实现类 EffectPort) |
| 掷格件 | 装配期格面资源摇号的承载件:池行装配、区间行 roll、权重合成(订阅加权/board_skew 两路投影);受理账面留守地图战斗资源件(机制归属同上;实现类 RewardRoller) |
| `seal_missed` | 封印落空事件(旧名已换代):封印动作落在安全格上时发出,订阅面据此做限次与豁免判定 |
| `forgive_seal_miss` | 本场首次封印落空豁免——专长「完美保险」的效果类型,`domain=battle` 首例;有效误标=误标总数 − 豁免数,豁免不改写误标账 |
@@ -107,7 +107,7 @@
| 整屏样张 | 整屏级样张——战斗/关卡/菜单/营地屏各出一张,验屏级文字对比度、明度分层与焦点预算 |
| 掩尘 | 未开格岩体顶面基色的专色名——覆尘完整岩体的「未知」面色(现行值 `#9C8462`,见美术圣经 §4.1) |
| 裸土 | 已开格连续地面基色的专色名——挖出空间的沉暗面色(现行值 `#695038`,见美术圣经 §4.1) |
| 色牌 | 已退役的棋盘数字底具——2026-11-07 浮空数字定案后棋盘不再使用数字底牌(沿革查美术圣经 §4.1) |
| 色牌 | 棋盘数字底具——普通数字已退役(2026-11-07 浮空数字定案,棋盘普通数字不再使用底牌);封印格身份字形色牌保留(2026-11-07 封印改版,叠于法阵之上);沿革查美术圣经 §4.1 |
| 浮空数字 | 棋盘数字的现行呈现——数字与格面字形浮在格上方,暖白字芯+2px 深墨描边+双色引导线;字高 21px、字底距格顶 15px(规格见[美术圣经](../art/art-bible.md) §3.2) |
| 图类型 | 棋盘生成产物 `board_type` 的中文名——三关换皮的单位(同款地形材质换色板) |
| 方向稿 | 冻结前的方向性候选稿,供气质目检;过目检后才进机检冻结 |
+52 -9
View File
@@ -127,6 +127,48 @@
> 呈现术语注记(2026-09-25):封印 = 正式动作名(全库统一;登记册术语与术语表;印记 = 封印印记,art-bible §3.2)——机制与键名不变;印记贴图已落位(`seal_mark.png`,环+十字法阵,形与定值见 art-bible §3.2/§8.2),缺图时回落封印黑「封」字形(棋盘视图程序化回退);封印特效的施印瞬间一件未实装。封印色 = 封印黑(现行)。
### 封印格呈现预览(2026-11-07 用户裁「封印受理即揭示」的呈现规格;2026-11-07 用户人审通过——按此先落实现,观感细化后置,不阻塞基线冻结)
封印受理后格子进入**已揭示+封印**态:墙格换已揭示连续地面,封印法阵(环+十字)叠在其上,格下身份字形(危险格「危」朱字/安全格数字)按既有绘制优先级(数字 > 小人 > 封印印记)叠于法阵之上、垫既有色牌机制保可读。棋盘重做批后承载面取本关受光棱色封印石(新连续地面 `#695038` 系对封印黑仅 2.35–2.98:1,不达 ≥4.5:1;受光棱系 6.45–9.94:1,零新增色,见 art-bible §3.2/§4.1)。轮末判定后印记不撤,随图存续。
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 560 128" width="560" height="128">
<defs>
<radialGradient id="sealhalo">
<stop offset="0.68" stop-color="#000000" stop-opacity="0"/>
<stop offset="0.82" stop-color="#000000" stop-opacity="0.5"/>
<stop offset="1" stop-color="#000000" stop-opacity="0"/>
</radialGradient>
</defs>
<rect x="0" y="0" width="560" height="128" fill="#2E2620"/>
<g font-family="monospace" font-size="12" fill="#C9AF83" text-anchor="middle">
<text x="52" y="122">未开(对照)</text>
<text x="148" y="122">封印·标中(危险格)</text>
<text x="244" y="122">封印·标空(安全格)</text>
<text x="340" y="122">已开(对照)</text>
</g>
<g>
<rect x="28" y="20" width="48" height="48" fill="#C9AF83"/>
<rect x="124" y="20" width="48" height="48" fill="#B39A78"/>
<circle cx="148" cy="44" r="24" fill="url(#sealhalo)"/>
<circle cx="148" cy="44" r="19.7" fill="none" stroke="#000000" stroke-width="4.3"/>
<rect x="143.9" y="28" width="8.2" height="32" fill="#000000"/>
<rect x="132" y="39.9" width="32" height="8.2" fill="#000000"/>
<rect x="136" y="32" width="24" height="24" fill="#D8C6A4"/>
<text x="148" y="52" font-family="monospace" font-size="17" font-weight="bold" fill="#962C24" text-anchor="middle">危</text>
<rect x="220" y="20" width="48" height="48" fill="#B39A78"/>
<circle cx="244" cy="44" r="24" fill="url(#sealhalo)"/>
<circle cx="244" cy="44" r="19.7" fill="none" stroke="#000000" stroke-width="4.3"/>
<rect x="239.9" y="28" width="8.2" height="32" fill="#000000"/>
<rect x="228" y="39.9" width="32" height="8.2" fill="#000000"/>
<rect x="232" y="32" width="24" height="24" fill="#D8C6A4"/>
<text x="244" y="52" font-family="monospace" font-size="17" font-weight="bold" fill="#33261B" text-anchor="middle">3</text>
<rect x="316" y="20" width="48" height="48" fill="#B39A78"/>
<text x="340" y="52" font-family="monospace" font-size="17" font-weight="bold" fill="#33261B" text-anchor="middle">2</text>
</g>
</svg>
像素级判据句(实现批验收锚):①印记参数沿用现行五常量单点(环外径 0.82 格边、环壁 0.11 环径、十字臂宽 0.156 环径、光圈 alpha 0.50/γ1.6),零新增美术件、零参数改动;②封印格承载 = 已揭示地面(棋盘重做批后为本关受光棱色封印石),印记居格心、环外留 9% 格宽光圈、光圈不外溢邻格;③身份字形(危/数字)垫既有色牌件叠于法阵之上——色牌取现行色板素色小牌件,墨字对色牌对比 ≥4.5:1;④封印黑对承载面对比:旧裸土 ≈7.8:1,棋盘重做批后受光棱 6.45–9.94:1(均 ≥4.5:1);⑤未开格与已开普通格观感按棋盘重做批新地形规格(对照列)。
> 可挖域微光画法定稿(2026-09-25 用户拍板):**接触带·空间侧 + 蓝族**——光画英雄格周界接触面(正交邻墙=整边,斜向邻墙=角点+两半边),详见 art-bible §3.2;现行墙格整框绿相呼吸为待退役占位,切换待表现侧实现(含探针断言)。
**布局决策 1(本 spec 最大决策,用户拍板)**:**常驻双舞台分屏**——战斗画面与扫雷图同屏常驻,挖掘期敌人待机可见,结算期不切场景。
@@ -162,8 +204,8 @@
| C | 奇物列表(现行标题「特性」) | 列表/图标行 | 已实装——逐件行「件名 ×N」呈现、零内部分类字样;锻造/大奖励/关卡奖励节点获得的属性提升——中循环数据,因改变战斗数学且无其他展示面准入战斗屏;与战斗内状态效果(B 区站桩)分区,防「战斗结束被清」误读 | 只读 | 已实装 |
| D | 行动资源侧栏(原 AP 条) | 两侧点阵 + 按钮组 | 左栏:本轮 AP 点阵、停手按钮;右栏:**RAP 点阵+数字(RAP 唯一消费口 = 弃图刷新,资源数字贴消费按钮——2026-09-26 拍板;RAP 清零可见反馈〔行动经济〕)**、图例按钮、弃图刷新按钮(RAP−8 · 本轮剩 N 次,上限 2 次为行动经济口径)。2026-11-07 顶栏搬两侧——原顶部整条 AP 条退役,数据按左/右栏分置,D 区全高归棋盘 | 点击提交意图 | HUD 视图拆分改造 |
| D | 棋盘地形层 | 合并岩体/连续地面 | 地面连续无缝;连通未开格(四邻)合并为抬升岩体(岩顶大板+南/东崖面+四段投影+整板悬崖);岩顶向北偏西抬升(LIFT/ESHIFT 比例见 art-bible §3.2);三关八色见 art-bible §4.1「三关棋盘环境八色」;不铺逐格瓦片;深度对象按脚底 y 排序,南侧岩体可遮住小人脚底(2026-11-07 用户裁) | 只读 | 新建(替换 cell_base_layer/tile_palette) |
| D | 棋盘浮空数字层 | 最顶层文字层 | 数字与格面字形(危/?)浮在格上方:字高 21px、2px 深墨描边、1px 浮空投影、暖白字芯、2px 双色引导线;字底阴影圆 10×6 画在开启格地面(格顶+7,位于小人之下,小人站该格时遮住圆);顶行数字越出棋盘上缘(D 区上方预留 44px);数字与引导线 z 最高,任何地形/小人/特效不遮数字与引导线;色牌退役 | 只读 | 新建 |
| D | 棋盘视图 | 网格 | 棋盘全部格态 + 小人 + 封印印记 + 变异纹带 + 奖励身份 icon(已开格;信息层叠放序 浮空数字>小人>封印印记>奖励徽章/类型微光>交互反馈>变异纹带>地形底色——浮空数字永不被遮,徽章不进数字区;屏幕层序按脚底行分档:北侧贴花低于小人、南侧随岩体在前,样张验收项) | 左/右键意图提交 | 改造 |
| D | 棋盘浮空数字层 | 最顶层文字层 | 数字与格面字形(危/?)浮在格上方:字高 21px、2px 深墨描边、1px 浮空投影、暖白字芯、2px 双色引导线;字底阴影圆 10×6 画在开启格地面(格顶+7,位于小人之下,小人站该格时遮住圆);顶行数字越出棋盘上缘(D 区上方预留 44px);数字与引导线 z 最高,任何地形/小人/特效不遮数字与引导线;封印格身份字形走色牌、不进浮空层;普通数字色牌退役 | 只读 | 新建 |
| D | 棋盘视图 | 网格 | 棋盘全部格态 + 小人 + 封印印记 + 变异纹带 + 奖励身份 icon(已开格;信息层叠放序 浮空数字>小人>封印印记>奖励徽章/类型微光>交互反馈>变异纹带>地形底色——浮空数字永不被遮,徽章不进数字区;屏幕层序按脚底行分档:北侧贴花低于小人、南侧随岩体在前;封印印记与封印格身份色牌固定低于小人,样张验收项) | 左/右键意图提交 | 改造 |
| D | 悬停预承诺光标 | 光标态 | 移动/挖/封印三语义光标 + 目标格边框高亮 | 自动 | 新建(排雷玩法×6③) |
| D | 可挖域指示 | 常驻微高亮 | 小人 8 向相邻可操作格 | 自动 | 新建(排雷玩法×6③) |
| D | 移动路径预览 | 路径高亮 | 悬停已开格显示 BFS 路径(合法性指示) | 自动 | 新建(排雷玩法×6⑤,裁决见下) |
@@ -226,7 +268,7 @@
### D 区布局与棋盘画法预览(2026-11-07 用户人审通过,内嵌备查)
> 预览依据 = art-bible §3.2/§4.1(v3.50)+本节布局表与格边公式。效果图是目标画面;像素判据以 1280×800 基座档钉死,实现批按此冻结基线(test-conventions §8.2)。
> 预览依据 = art-bible §3.2/§4.1(v3.51)+本节布局表与格边公式。效果图是目标画面;像素判据以 1280×800 基座档钉死,实现批按此冻结基线(test-conventions §8.2)。
<svg viewBox="0 0 1036 548" width="100%" style="min-width:760px" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="battle-d-title battle-d-desc">
<title id="battle-d-title">D 区布局——顶栏搬两侧、棋盘全高、上方 44px 浮空数字预留</title>
@@ -307,8 +349,8 @@
| 浮空字 | 字高 21、字底距格顶 15、视觉顶距格顶 38;2px 深墨描边+1px 浮空投影;引导线 2px(左深右浅),自格顶−12 到格顶+4;字底阴影圆 10×6、圆心格顶+7,画在开启格地面、位于小人之下 |
| 岩体投影 | LIFT=round(38×0.435)=17;ESHIFT=round(38×0.109)=4;相邻岩顶搭接 2;板边岩顶外延 ESHIFT;南崖高 17、宽 42(38+4)、顶缘 2px 口沿高光取本关受光棱(dungeon #C9AF83)+1px 深墨线;东崖宽 4、高 55(38+17);北/西临空缘 2px 主亮棱+2px 次亮棱+外 2px 深墨边;南/东临空缘 2px 深墨内缘+3 段×2px 内阴影(自缘内 0.217×格边起,α0.27/0.16/0.08);南/东投影 4 段×4px 渐隐(α0.44/0.30/0.16/0.07);整板悬崖 8 |
| 深度遮挡 | 深度键 = 脚底格底 y;岩体取最南格格底;升序绘制;南侧岩体(键更大)画在小人之后、遮住小人脚底 |
| 层序 | 地面→投影→开启格地面阴影圆(小人之下)→深度对象(岩体+小人;格件顶面贴花〔封印/微光/变异〕与交互反馈随脚底行同序——北侧低于小人、南侧随岩体在前)→浮空数字+引导线(z 最高) |
| 顶面/视觉 rect | 墙格顶面 rect = 格矩形左上偏移 (−ESHIFT,−LIFT)、边长=格边;整格视觉 rect = 顶面向右补 ESHIFT、向下补 LIFT;封印/微光/变异锚顶面 rect,迷雾黑罩覆盖顶面+南面+东面三块 |
| 层序 | 地面(封印格地面铺受光棱色封印石)→投影→开启格地面阴影圆(小人之下)→深度对象(岩体+小人;格件顶面贴花〔微光/变异〕与交互反馈随脚底行同序——北侧低于小人、南侧随岩体在前;封印印记与封印格身份色牌固定低于小人)→浮空数字+引导线(z 最高) |
| 顶面/视觉 rect | 墙格顶面 rect = 格矩形左上偏移 (−ESHIFT,−LIFT)、边长=格边;整格视觉 rect = 顶面向右补 ESHIFT、向下补 LIFT;微光/变异锚顶面 rect;封印锚逻辑格心;迷雾黑罩覆盖顶面+南面+东面三块 |
| 悬停/路径 rect | 墙格悬停金框按顶面 rect;已开地面格悬停/路径点迹按逻辑 rect(不偏移) |
| 禁裁切 | 顶行数字、引导线、阴影圆全部落在 D 区内;任何地形/小人/特效不遮浮空数字与引导线 |
@@ -331,7 +373,7 @@
| **调试变体** | 测试模式 | 调试浮窗入口发布态恒隐(无 UI 开启口;开发期临时启用=本地改该字段缺省、不提交——调参面板已退役);Playtest 诊断按钮(卡死转储) |
**拒绝/作废子态**(排雷玩法×6⑥、行动经济双源——每次拒绝玩家可感知):
- 棋盘面拒绝(细分原因:非相邻/挖封印格等)→ 目标格 危险红 #A8321F 边框闪 + toast 文案(拒绝语义红族——现行封印黑)(图标+文案冗余,色弱清单 #7)
- 棋盘面拒绝(细分原因:非相邻/挖已封印格等)→ 目标格 危险红 #A8321F 边框闪 + toast 文案(拒绝语义红族——现行封印黑)(图标+文案冗余,色弱清单 #7)
- 资源面拒绝(AP 不足/RAP 不足)→ AP/RAP 数字脉冲 + toast「AP 不足 · 剩 0 点」
- voided(stop 被作废等)→ toast「本轮已清图,停手未执行 · 剩余 AP 已作废」——防静默改判读感欺骗
@@ -345,7 +387,8 @@
|---|---|---|---|---|
| 未开相邻格 | 挖掘 | 左键 | 格碎裂 + 连锁展开(免费)+ 奖励身份揭显 | 意图入当帧批次 → 排雷玩法的意图批次提交 |
| 已开格 | 移动 | 左键 | 悬停时路径预览高亮(布局决策 3);执行 ≤150ms 短滑 | 意图入批(BFS 移动,免费) |
| 未开格 | 封印 | 右键 | 封印印记落下(封印黑) | 意图入批(1 AP / 免费;已封印格右键无操作——封印印记无移除手段,封印不可逆) |
| 未开格 | 封印 | 右键 | **受理即揭示**:墙格换已揭示地面 + 封印印记落下(封印黑)+ 格下身份揭显(危/数字,垫色牌) | 意图入批(1 AP / 免费;受理当场零兑付——兑付在轮末判定批) |
| 已封印格(已揭示) | 无 | 左键/右键 | 禁止光标 + 悬停说明(封印格无可用动作) | 零意图(不入批——封印格拒绝挖掘、不可通行、不可再封印;封印印记无移除手段,封印不可逆) |
| 停手按钮 | 主动结束轮次 | 左键 | 按钮下压 + AP→RAP 飞行数字 | 停手随当帧意图批次提交(**经排雷玩法批次,不直调行动经济**——单意图帧批次契约) |
| 弃图刷新按钮(D 区右栏) | 当轮就地换图继续挖 | 左键 | 确认微交互(RAP−8 代价显式 + 旧板弃置提示);RAP<8 → 资源面拒绝可见(行动经济 EC13) | 刷新意图提交(**Active 相位当场换图继续挖**——AP 不重置、本轮剩 N 次扣减;受理口径见布局决策 4/OQ11) |
| 图例按钮 | 开图例 | 左键 | 模态 overlay | 恒可开(图例条目恒在) |
@@ -357,7 +400,7 @@
**复合意图(排雷玩法评估项)**:点击远格 = 自动 move+dig 的两击摩擦消解——**MVP 不做**(免费移动高频,两击摩擦符合 P4 ≤2 观测假设;排雷玩法 L298 评估项在案)。
**可挖域常驻指示**:小人 8 向相邻未开格呼吸微光(§2.1 唯一在动的元素);不可挖域(非相邻)悬停显示禁止光标 + 首次 toast 提示「需先走近(点击已开格移动,免费)」。
**可挖域常驻指示**:小人 8 向相邻未开格呼吸微光(§2.1 唯一在动的元素);不可挖域(非相邻)悬停显示禁止光标 + 首次 toast 提示「需先走近(点击已开格移动,免费)」。已封印格(含相邻)悬停显示禁止光标——封印格拒绝挖掘、不可通行、不可再封印,无动作可发(封印格呈现规格见「封印格呈现预览」节)。
**chord 肌肉记忆首遇提示**(排雷玩法×6③):经典扫雷玩家首日尝试双击快开时,非模态 toast 每 run 一次:「本作无双击快开——封印后轮末自动判定标中;点数字格可移动小人」(含排雷玩法登记锚点「点击数字格 = 移动小人」)。文案定稿权归实现期文案 pass。
@@ -459,7 +502,7 @@
- [ ] 两种格态(已开格 / 未开格)灰度截图下一眼可辨(排雷玩法×6②)
- [ ] 悬停光标区分移动/挖/封印三语义;可挖域(8 向相邻)常驻指示;非相邻格悬停显禁止反馈(排雷玩法×6③)
- [ ] 已引爆危险格呈"可通行可读"(排雷玩法×6④)
- [ ] 浮空数字按 art-bible §1.4 第一验收项的复合对比判据过门(四类底上 max(字芯对底,描边对底) ≥4.5 且字芯对描边 ≥4.5);明度分层规则无违例(art-bible §2/§4.1——岩顶/地面功能分档合法,格态语义由形与色相承载);棋盘绘制路径零色牌调用
- [ ] 浮空数字按 art-bible §1.4 第一验收项的复合对比判据过门(四类底上 max(字芯对底,描边对底) ≥4.5 且字芯对描边 ≥4.5);明度分层规则无违例(art-bible §2/§4.1——岩顶/地面功能分档合法,格态语义由形与色相承载);棋盘普通数字绘制路径零色牌调用(封印格身份字形色牌为唯一例外)
**交互与反馈**
+1 -1
View File
@@ -26,7 +26,7 @@
**场景按要素分层(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%),其上是天空与远山、其下整片地面;**中景围墙带 y220–420(高 200)**,素材 1280×164 按 200 高显示拉伸(桩身/门柱比例随缩放);**墙脚压入地面纹理(入土线,2026-11-07)**;近景物件允许压住围墙(围墙不可点,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 提交史。
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 46 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
## 同调配装件:装备中的秘宝件对象集唯一所有者(每件内含充能单位与重置周期——冒险域拆分姊妹批自共享充能账字典轨迁移)。
## 同调配装件:装备中的秘宝件对象集唯一所有者(每件内含充能单位与重置周期)。
## 同调充能单源化:物化行(treasure_inventory 内 source_class="attunement" 行)为使用面唯一事实源——
## 剩余次数由行的未用数派生(唯一读法,无影子账);字典轨与三处事后差额对账退役,改「写时种行/补行」。
## 种行/补行写时同步:冒险开始全量种行(不筛周期——battle/level 周期件开局即有行,C 区投影行数不变);
+2 -2
View File
@@ -49,8 +49,8 @@ var _enemy_system: UnitContainer
var _status_effects: StatusContainer
var _reward_economy: RewardEconomy
var _damage: DamageController # 伤害控制器(单据唯一兑付管线)
var _fx_host: BattleFxHost # 效果引擎宿主件(fx 族持有与兑付——拆件定稿轴件一)
var _reward_grant: RewardGrant # 奖励授予件(授予管线与秘宝使用——拆件定稿轴件二)
var _fx_host: BattleFxHost # 效果引擎宿主件(fx 族持有与兑付)
var _reward_grant: RewardGrant # 奖励授予件(授予管线与秘宝使用)
var round_index := 0
var battle_result := "" # "" / "victory" / "defeat" / "fatal_error"
+5 -2
View File
@@ -414,12 +414,15 @@ func grant_clear_reward(ri: int, skip_on_refresh: bool, perfect: bool) -> void:
if not (snap is Dictionary):
return
var first_ok := _grant_clear_identity(snap, ri, "(清图奖励)")
# 完美封印追加兑付倍率读表(数值单一事实源——p_perfect 经 ConfigDB settlement_core 行,
# 载体域 {100,200}:200=×2 追加第二份、100=×1 单份不发。追加份数结构上限 1 份不变)
var perfect_mult := int(db.combat.settlement.core.p_perfect) / 100
var second_ok := false
if perfect and first_ok:
if perfect and first_ok and perfect_mult >= 2:
second_ok = _grant_clear_identity(snap, ri, "(完美第二份)")
if perfect and first_ok:
emit_cb.call({"type": "highlight", "source_class": "clear_reward", "origin_cell": Vector2i(-1, -1), "highlight": "perfect_sweep"},
ri, "完美封印!清图奖励 ×2", (2 if second_ok else 1), {})
ri, "完美封印!清图奖励 ×%d" % perfect_mult, (1 + (1 if second_ok else 0)), {})
# 清图奖励 = 整图独立对象(不属于任何一格):兑付形态按其 cell_type 的正向字典形态走,
+1 -1
View File
@@ -6,7 +6,7 @@ extends Resource
@export var canvas := Vector2(1280, 800) # 终稿画布(工程档同幅面)
@export var hud_band_h := 48 # 顶栏禁区高度(命中不侵)
@export var gate_gap := Vector2i(520, 760) # 门洞缺口 x 范围(透出背景层小路,禁填死)
@export var wall_band := Rect2(0, 240, 1280, 70) # 围墙带包络(门柱顶 240—桩底 310;围墙贴图须落在其内)
@export var wall_band := Rect2(0, 220, 1280, 200) # 围墙带包络(显示矩形 (0,220,1280,200);围墙贴图须落在其内)
@export var layers: Array[CampSceneLayerSpec] = []
+17 -17
View File
@@ -13,64 +13,64 @@ hotspot_key = &""
[sub_resource type="Resource" id="2"]
script = ExtResource("2")
key = &"wall"
texture_rect = Rect2(0, 261, 1280, 49)
texture_rect = Rect2(0, 220, 1280, 200)
hotspot = Rect2(0, 0, 0, 0)
hotspot_key = &""
[sub_resource type="Resource" id="3"]
script = ExtResource("2")
key = &"board"
texture_rect = Rect2(845, 268, 130, 121)
hotspot = Rect2(845, 268, 130, 140)
texture_rect = Rect2(845, 241, 130, 121)
hotspot = Rect2(845, 241, 130, 140)
hotspot_key = &"board"
[sub_resource type="Resource" id="4"]
script = ExtResource("2")
key = &"gate"
texture_rect = Rect2(520, 250, 240, 211)
hotspot = Rect2(520, 250, 240, 220)
texture_rect = Rect2(520, 282, 240, 138)
hotspot = Rect2(520, 282, 240, 147)
hotspot_key = &"gate"
[sub_resource type="Resource" id="5"]
script = ExtResource("2")
key = &"dummy"
texture_rect = Rect2(340, 520, 120, 180)
hotspot = Rect2(340, 520, 120, 180)
texture_rect = Rect2(340, 474, 120, 180)
hotspot = Rect2(340, 474, 120, 180)
hotspot_key = &"dummy"
[sub_resource type="Resource" id="6"]
script = ExtResource("2")
key = &"forge"
texture_rect = Rect2(40, 467, 240, 193)
hotspot = Rect2(40, 420, 240, 240)
texture_rect = Rect2(40, 421, 240, 193)
hotspot = Rect2(40, 374, 240, 240)
hotspot_key = &"forge"
[sub_resource type="Resource" id="7"]
script = ExtResource("2")
key = &"tent"
texture_rect = Rect2(982, 520, 251, 170)
texture_rect = Rect2(982, 335, 251, 170)
hotspot = Rect2(0, 0, 0, 0)
hotspot_key = &""
[sub_resource type="Resource" id="8"]
script = ExtResource("2")
key = &"chest"
texture_rect = Rect2(1097, 700, 106, 90)
hotspot = Rect2(1085, 700, 130, 90)
texture_rect = Rect2(1097, 654, 106, 90)
hotspot = Rect2(1085, 654, 130, 90)
hotspot_key = &"chest"
[sub_resource type="Resource" id="9"]
script = ExtResource("2")
key = &"notebook"
texture_rect = Rect2(818, 704, 100, 66)
hotspot = Rect2(818, 690, 100, 80)
texture_rect = Rect2(870, 515, 100, 66)
hotspot = Rect2(870, 501, 100, 80)
hotspot_key = &"notebook"
[sub_resource type="Resource" id="10"]
script = ExtResource("2")
key = &"campfire"
texture_rect = Rect2(560, 571, 160, 149)
hotspot = Rect2(560, 640, 160, 80)
texture_rect = Rect2(560, 525, 160, 149)
hotspot = Rect2(560, 594, 160, 80)
hotspot_key = &"campfire"
[resource]
@@ -78,5 +78,5 @@ script = ExtResource("1")
canvas = Vector2(1280, 800)
hud_band_h = 48
gate_gap = Vector2i(520, 760)
wall_band = Rect2(0, 240, 1280, 70)
wall_band = Rect2(0, 220, 1280, 200)
layers = [SubResource("1"), SubResource("2"), SubResource("3"), SubResource("4"), SubResource("5"), SubResource("6"), SubResource("7"), SubResource("8"), SubResource("9"), SubResource("10")]
@@ -133,6 +133,7 @@ func _build_layer_node(spec: CampSceneLayerSpec) -> Control:
if tex != null:
var tr := TextureRect.new()
tr.texture = tex
tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE # 允许节点小于贴图原尺寸(门扇 240×211→矩形 138 高)
tr.stretch_mode = TextureRect.STRETCH_SCALE
node = tr
else:
+82 -4
View File
@@ -23,10 +23,77 @@ var _level_readings := {} # 关 → 双帧比对读数(实现报告读数表
var _cur_settlement = null # 当关 settlement(身份回调读取面——仿生产 run_scope.settlement 现值;
# 不用 Callable.bind 传参:bind 的 Callable 动态派发时对类型化参数做 Object 硬校验,
# 报「Cannot convert argument 2 from Object to Vector2i」——直接方法引用+成员现值无此坑)
var _cur_mark_counts := {} # 当关 cell_mark_overlay 绘制域逐类型格计数(注入后读数——V14 采集态核对)
func _probe_settlement_getter():
return _cur_settlement
# 绘制域缺型注入(确定性直构——每关固定选格,同种子同态可复现):
# 真视框=首个未开未封印危险格;预读徽章=身份查询命中的未开格(循环挑到命中,全空记零);
# 移除退色=remove_mines_in 真实写口移除一个未揭示危险格;点迹/高亮=英雄格 face 直设定格。
func _mark_domain_inject(view: Node, settlement: Object) -> void:
var ms = settlement.minesweeper
var board = ms.current_board()
var reveal_cell := Vector2i(-9, -9)
var preview_cell := Vector2i(-9, -9)
var remove_cell := Vector2i(-9, -9)
for y in board.height:
for x in board.width:
var c := Vector2i(x, y)
if ms.is_revealed(c) or ms.has_seal(c) or ms.is_removed(c):
continue
if board.is_mine(c):
if reveal_cell.x < 0:
reveal_cell = c
if remove_cell.x < 0:
remove_cell = c
elif preview_cell.x < 0 and _probe_identity_lookup(c) != null:
preview_cell = c
if remove_cell.x >= 0:
ms.remove_mines_in([remove_cell])
view.set_treasure_overlays([reveal_cell] if reveal_cell.x >= 0 else [],
[preview_cell] if preview_cell.x >= 0 else [])
view.refresh()
var hero: Vector2i = ms.hero_pos()
var cells: Dictionary = view.cells_probe()
if cells.has(hero):
(cells[hero] as Node2D).face.dot_visible = true
var nb: Vector2i = hero + Vector2i(1, 0)
if not cells.has(nb):
nb = hero + Vector2i(0, 1)
if cells.has(nb):
(cells[nb] as Node2D).face.hl_visible = true
_cur_mark_counts = {"injected": {"reveal": str(reveal_cell), "preview": str(preview_cell),
"remove": str(remove_cell), "dot": str(hero)}}
# 逐类型格计数读数(off 帧呈现态——face 现值直读,CellUnit 在册探针读口同面)
func _mark_domain_counts(view: Node, tag: String) -> Dictionary:
var counts := {"mutation_mark": 0, "treasure_mark": 0, "badge_glyph": 0,
"dot_visible": 0, "hl_visible": 0, "removed_dim": 0}
var cells: Dictionary = view.cells_probe()
for c in cells:
var f = (cells[c] as Node2D).face
if String(f.mutation_mark) != "":
counts["mutation_mark"] = int(counts["mutation_mark"]) + 1
if String(f.treasure_mark) != "":
counts["treasure_mark"] = int(counts["treasure_mark"]) + 1
if String(f.badge_glyph) != "":
counts["badge_glyph"] = int(counts["badge_glyph"]) + 1
if bool(f.dot_visible):
counts["dot_visible"] = int(counts["dot_visible"]) + 1
if bool(f.hl_visible):
counts["hl_visible"] = int(counts["hl_visible"]) + 1
if bool(f.removed_dim):
counts["removed_dim"] = int(counts["removed_dim"]) + 1
print("[board_shot] mark_domain %s: %s" % [tag, counts])
return counts
func _ready() -> void:
# oracle: design/engineering/test-conventions.md:84 (棋盘截图取证——渲染闸采集组)
# 判据出处: design/engineering/test-conventions.md:84 (棋盘截图取证——渲染闸采集组)
# 运行前提=窗口化真渲染面(shot_probe 同款护栏——headless 下 frame_post_draw 永不触发+视口纹理恒空)
if DisplayServer.get_name() == "headless":
print("[board_shot] FAIL — 本探针必须窗口化运行(headless 无渲染面,截图必空);现行事实源=tools/render_gate.sh 取证段")
@@ -120,7 +187,17 @@ func _ready() -> void:
# 双帧比对空转、PASS 假绿)。现值经 _cur_settlement 成员读入
# (仿生产 run_scope.settlement;bind 传参有 Callable 类型校验坑,见成员注)。
view.set_identity_resolver(_probe_identity_lookup)
# 变异角标 resolver(生产同源——main_ui_build:262 set_variant_mark_resolver 接线同款):
# 裸视图不接 = mutation_mark 恒空 = L3 伤害型变异角标缺帧。rebuild 前接(paint 链读回调)。
var resolvers = load("res://presentation/board_resolvers.gd").new(db, null, _probe_settlement_getter)
view.set_variant_mark_resolver(resolvers.variant_mark_lookup)
view.rebuild(settlement.minesweeper.current_board(), settlement.minesweeper)
# ---- cell_mark_overlay 绘制域采集态注入(卡修轮 2 V14——确定性直构,缺型补进采集态)----
# 五类型注入全走真链/生产同源:真视框与预读徽章=set_treasure_overlays(生产呈现链);
# 移除退色=remove_mines_in(核心真实写口——移除集=未揭示无封印是雷,判定基线零回溯);
# 点迹/高亮=u.face 直设(CellUnit 探针读口同面的写侧,交互瞬态的定格化)。
_mark_domain_inject(view, settlement)
var mark_counts := _mark_domain_counts(view, tag)
# 进挖掘相位(生产态 = hud_presenter.set_dig_glow_enabled(not settling) 接线——裸视图默认
# _dig_glow=false,接触带与类型微光同源门控都不渲染;golden 基线即捕获于
# 该裸态。双帧取证要求生产默认态,故显式开——两帧均含静态接触带(motion_scale=0.5 停格),
@@ -182,7 +259,7 @@ func _ready() -> void:
# 判据:环外径像素 ÷ 格边 落 0.82±1px;封印格最外圈 1px 框最暗值与相邻未封印未开格同位框最暗值差 ≤1/255
#(halo 在 0.455×格边归零——格边框不应被压暗;相邻未开格同砖纹理同套瓦片为无封印参照)。
func _seal_evidence(tag: String, view: Node, settlement: Object, off_img: Image, out: String) -> void:
# oracle: design/engineering/test-conventions.md:84 (封印印记像素取证组)
# 判据出处: design/engineering/test-conventions.md:84 (封印印记像素取证组)
var ms = settlement.minesweeper
var seal_cell := Vector2i(-9, -9)
var ref_cell := Vector2i(-9, -9)
@@ -290,7 +367,7 @@ func _probe_identity_lookup(c: Vector2i):
# 双帧逐像素比对:所有变化像素必须落在生效格的内缘带内
# (de < cell÷8——带外含砖心零侵入=禁整格染色/禁越圈);每个生效格须有实绘像素(不空转)。
func _check_dual_frames(tag: String, view, glow_cells: Dictionary, off_img: Image, on_img: Image) -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var board = view.board()
if board == null:
_problems.append("%s 双帧比对取板失败(view.board() 为 null)" % tag)
@@ -342,7 +419,8 @@ func _check_dual_frames(tag: String, view, glow_cells: Dictionary, off_img: Imag
if first_outside == "":
first_outside = "%s 非生效格" % str(c2)
_level_readings[tag] = {"glow_cells": glow_cells.size(), "changed_total": changed_total,
"changed_outside": changed_outside, "band_px": band_px}
"changed_outside": changed_outside, "band_px": band_px,
"mark_domain": _cur_mark_counts}
print("[board_shot] %s 双帧读数 glow=%d changed=%d outside=%d band=%.0fpx"
% [tag, glow_cells.size(), changed_total, changed_outside, band_px])
if changed_outside != 0:
+1 -1
View File
@@ -8,7 +8,7 @@ extends Node
func _ready() -> void:
# oracle: design/gdd/systems/战斗循环/战斗表现.md:148 (结算演出——挖掘期零打扰组)
# 判据出处: design/gdd/systems/战斗循环/战斗表现.md:148 (结算演出——挖掘期零打扰组)
await get_tree().process_frame
var problems: Array = []
+3 -3
View File
@@ -21,7 +21,7 @@ var _problems: Array = []
func _ready() -> void:
# oracle: design/engineering/test-conventions.md:84 (特效截图取证——渲染闸采集组)
# 判据出处: design/engineering/test-conventions.md:84 (特效截图取证——渲染闸采集组)
var args := OS.get_cmdline_user_args()
out_dir = args[0] if args.size() >= 1 and args[0] != "" else "user://qa-fx"
DirAccess.make_dir_recursive_absolute(out_dir)
@@ -108,7 +108,7 @@ func _ready() -> void:
func _snap(tag: String) -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
await RenderingServer.frame_post_draw
var img: Image = get_viewport().get_texture().get_image()
if img == null or img.is_empty():
@@ -118,7 +118,7 @@ func _snap(tag: String) -> void:
func _fail(msg: String) -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
_problems.append(msg)
push_error("[fx_shot_probe] FAIL — " + msg)
get_tree().quit(1)
+2 -2
View File
@@ -19,7 +19,7 @@ var _est_reads: Dictionary = {} # 逐档 13/14 板推定读数留痕(label
func _ready() -> void:
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264 (战斗屏三区几何与棋盘格边长——分辨率档位组)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:264 (战斗屏三区几何与棋盘格边长——分辨率档位组)
await get_tree().process_frame
var main: Node = load("res://presentation/main.tscn").instantiate()
add_child(main)
@@ -59,7 +59,7 @@ func _ready() -> void:
# 一份不变量断言,逐档代入——代码量与档数无关
func _check_one(label: String, main: Node) -> void:
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264 (单档几何断言组)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:264 (单档几何断言组)
var vr: Rect2 = get_viewport().get_visible_rect()
if vr.size != Vector2(VIEWPORTS[0]["size"]) and vr.size.y < 1.0:
_problems.append("[%s] 逻辑视口退化 %s" % [label, vr.size])
+3 -3
View File
@@ -11,7 +11,7 @@ extends Node
const SEED_L3 := 910212 # 深图固定种子(与 level_map_viewport_probe 同源——图确定性承载)
func _ready() -> void:
# oracle: design/engineering/test-conventions.md:84 (关卡画面截图取证——载入守卫组)
# 判据出处: design/engineering/test-conventions.md:84 (关卡画面截图取证——载入守卫组)
var args := OS.get_cmdline_user_args()
var out_dir := args[0] if args.size() > 0 else "user://"
await get_tree().process_frame
@@ -45,7 +45,7 @@ func _settle() -> void:
func _make_runner(entry: bool, node_id: int):
# oracle: design/engineering/test-conventions.md:84 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:84 (判定上报通道——验证函数组)
var rng = load("res://core/rng_service.gd").new()
rng.new_adventure(SEED_L3)
var runner = load("res://core/level_runner.gd").new(get_node("/root/ConfigDB"), rng)
@@ -85,7 +85,7 @@ func _deepest_with_outs(screen, runner) -> int:
func _shot(out_dir: String, fname: String) -> void:
# oracle: design/engineering/test-conventions.md:84 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:84 (判定上报通道——验证函数组)
DirAccess.make_dir_recursive_absolute(out_dir) # 落盘目录自建(save_png 对缺目录静默失败——刷新链不预建)
var img := get_viewport().get_texture().get_image()
var err := img.save_png(out_dir.path_join(fname))
+11 -11
View File
@@ -22,14 +22,14 @@ var _runner # 当前停点的单关 runner(open_choices 驱动随记——
func _check(desc: String, ok: bool) -> void:
# oracle: design/ux/level-map.md:3 (判定上报通道——验证函数组)
# 判据出处: design/ux/level-map.md:3 (判定上报通道——验证函数组)
_checks += 1
if not ok:
_problems.append(desc)
func _ready() -> void:
# oracle: design/ux/level-map.md:3 (关卡画面视口跟随——卷轴视口组)
# 判据出处: 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/ux/level-map.md:3 (浅层不裁切组)
# 判据出处: 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/ux/level-map.md:3 (深层入口钳制组)
# 判据出处: 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/ux/level-map.md:3 (判定上报通道——验证函数组)
# 判据出处: 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/ux/level-map.md:3 (判定上报通道——验证函数组)
# 判据出处: 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/ux/level-map.md:3 (判定上报通道——验证函数组)
# 判据出处: 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/ux/level-map.md:3 (判定上报通道——验证函数组)
# 判据出处: 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/ux/level-map.md:3 (判定上报通道——验证函数组)
# 判据出处: 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/ux/level-map.md:3 (判定上报通道——验证函数组)
# 判据出处: 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/ux/level-map.md:3 (判定上报通道——验证函数组)
# 判据出处: design/ux/level-map.md:3 (判定上报通道——验证函数组)
if _checks == 0:
_problems.append("零断言执行(断言数下限闸——三硬闸③)")
if _problems.is_empty():
+38 -1
View File
@@ -14,6 +14,11 @@
# 锚(具名节点用其名、带脚本节点用脚本文件名;@ 临时名段整体不入键),控件本体记 类#钮面文本,
# 同键控件按接线遍历序追加 #序位。键在确定构建序下可复现;UI 构建序变更会平移同键序位——
# 表现为键漂移而非静默丢失,属测量面可接受代价。
# 开发面边界(2026-11-07 用户裁——调试面不入玩家可达对账宇宙,登记层过滤、不产豁免条目):
# DebugOverlay 浮窗(class_name 声明,main_ui_build 装配注「玩家画面零常驻调试件」)+
# ui.screens.debug_badge 调试角标+ui.screens.inter_overlay 调试两层模式专用层
# (main.gd _on_battle_finished「仅调试模式接线」——正常冒险层间推进归 AdventureRunner)。
# 依据=test-conventions「调试注入两层」调试面发布态恒隐条款;过滤为纯减法(玩家可达面零影响)。
# 用法:探针 _ready 里 `_recorder = ProbeCoverageRecorder.new()` → `attach(main, "<探针名>")`;
# 收尾 `dump_js()`。探针共用面不入 S 系扫描面(code-conventions §4.13 分档)。
extends RefCounted
@@ -29,6 +34,8 @@ var _node_keys: Dictionary = {} # 接线时分配的节点键(instanc
var _key_seen: Dictionary = {} # 基键出现次数(同键控件按遍历序追加序位)
var _wired: Dictionary = {} # 已连 pressed 的按钮实例 id → true(防重连)
var _skipped_temp := 0 # 临时名命中笔数(不入 key,只计数)
var _dev_roots: Array[Node] = [] # 开发面根(DebugOverlay 浮窗/调试角标/inter 调试专用层)——整棵子树不入账
var _dev_skipped := 0 # 开发面接线跳过笔数(观测面——dump meta 携带)
# ---- 装载与接线 ----
@@ -37,10 +44,32 @@ func attach(main: Node, probe_name: String) -> void:
_main = main
_probe_name = probe_name
ProbeUiUtil.coverage_hook = self # 共用件真输入口统一过旗标(push_click/push_click_at/push_motion)
_resolve_dev_roots()
_wire_buttons()
_wire_camp_scene()
# 开发面根解析(三处成员引用/脚本身份单点,见头注边界声明)——解析仅 attach 时一次,
# 解析不到的根跳过(无独立计数;attach 后重建的开发面根会漏滤)——漏滤方向由 seen 集
# 「开发面键 0」核对兜住(开发面键再现=过滤失守),玩家可达面零误滤风险(早退只命中三根)。
func _resolve_dev_roots() -> void:
var overlay := _find_by_script(_main, "debug_overlay")
if overlay != null:
_dev_roots.append(overlay)
var ui: Object = _main.get("ui")
if ui != null:
var screens: Object = ui.get("screens")
if screens != null:
for prop in ["inter_overlay", "debug_badge"]:
var n: Node = screens.get(prop)
if n != null:
_dev_roots.append(n)
func _is_dev_root(n: Node) -> bool:
return _dev_roots.has(n)
# 懒重扫:每次真输入 push 前接线一遍新入树的按钮(开层重建的钮行在下次点击前接上);
# 已释放按钮的连接随节点释放失效,登记表按 instance_id 清扫。
func mark_real_input() -> void:
@@ -63,6 +92,9 @@ func _wire_buttons() -> void:
func _wire_into(node: Node) -> void:
if _is_dev_root(node): # 开发面整棵子树不入账(接线与命中双断——pressed 沿都不连)
_dev_skipped += 1
return
if node is BaseButton:
var id := node.get_instance_id()
if not _wired.has(id):
@@ -184,6 +216,9 @@ func enumerate_seen() -> Array[String]:
func _seen_into(node: Node, keys: Array[String]) -> void:
if _is_dev_root(node): # seen 枚举与登记同源同滤——开发面不入「存在但零覆盖」缺口面
_dev_skipped += 1
return
if node is BaseButton:
var id := node.get_instance_id()
var key := String(_node_keys.get(id, ""))
@@ -199,7 +234,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 (覆盖登记落盘守卫——判定上报通道组)
# 判据出处: design/engineering/test-conventions.md:30 (覆盖登记落盘守卫——判定上报通道组)
var dir := DirAccess.open("res://")
if dir == null:
push_error("[coverage] res:// 打不开,登记未落盘")
@@ -228,6 +263,8 @@ func dump_js() -> void:
"hits_total": total,
"seen_total": seen.size(),
"skipped_temp_names": _skipped_temp,
"dev_face_roots": _dev_roots.size(),
"dev_face_skipped": _dev_skipped,
},
"hits": _hits,
"seen": seen,
+18 -4
View File
@@ -198,16 +198,30 @@ static func window_panel(view: Control) -> PanelContainer:
# 判据:钮 rect 整体落在窗板内(上缘留 8px、下缘让开底栏 64px——返回营地钮带高约 56px)。
# 保险丝 60 次滚叶防异常内容死循环;窗板缺失或钮句柄失效即返回(调用方前置断言兜底)。
static func scroll_button_into_view(main, view: Control, btn: Control) -> void:
var panel := window_panel(view)
if panel == null or btn == null:
if btn == null:
return
# 可视区基准=按钮最近的 ScrollContainer 祖先(滚动裁剪的真实可视域);
# 无滚动祖先时回退窗壳面板(旧基准——按钮不在滚动容器内的窗体形态)。
# 修因(迭代 55):秘宝匣 _ws_scroll 336px 高于窗壳面板内部——以面板为基准时
# 裁剪区外按钮被误判「在视」,滚轮零滚动、真点落空(实证:购买 25/装配/升级 零命中)。
var panel: Control = null
var anc: Node = btn.get_parent()
while anc != null and anc is Control:
if anc is ScrollContainer:
panel = anc
break
anc = anc.get_parent()
if panel == null:
panel = window_panel(view)
if panel == null:
return
var guard := 60
while guard > 0:
var r := btn.get_global_rect()
var pr := panel.get_global_rect()
if r.position.y >= pr.position.y + 8.0 and r.end.y <= pr.end.y - 64.0:
if r.position.y >= pr.position.y + 8.0 and r.end.y <= pr.end.y - 8.0:
return
var down := r.end.y > pr.end.y - 64.0
var down := r.end.y > pr.end.y - 8.0
push_wheel(main, pr.get_center(), 1 if down else -1)
guard -= 1
await main.get_tree().process_frame
+13 -13
View File
@@ -76,14 +76,14 @@ func _seals_text(f: Dictionary) -> String:
# ---- 流程状态位(与可见性是两回事,逐步单独判)----
func _expect_adventure_seal(step: String, expect: bool) -> void:
# oracle: design/engineering/architecture/architecture.md:168 (状态位判据组)
# 判据出处: design/engineering/architecture/architecture.md「呈现层装配与会话生命周期」 (状态位判据组)
var got: bool = main.run_scope != null and main.run_scope.is_adventure
if got != expect:
_problems.append("%s:流程状态位 is_adventure 应为 %s,实测 %s" % [step, str(expect), str(got)])
func _expect_exclusive(step: String, expect: String) -> void:
# oracle: design/engineering/architecture/architecture.md:168 (屏级互斥组)
# 判据出处: design/engineering/architecture/architecture.md「呈现层装配与会话生命周期」 (屏级互斥组)
var f: Dictionary = _screen_seals()
var pairs := [["菜单", "营地"], ["菜单", "冒险"], ["营地", "冒险"], ["终局", "营地"], ["终局", "菜单"]]
for pair in pairs:
@@ -110,7 +110,7 @@ func _frames(n: int = 2) -> void:
# ---- ① boot:菜单独占 ----
func _step_boot_menu() -> void:
# oracle: design/engineering/test-conventions.md:49 (boot 组)
# 判据出处: design/engineering/test-conventions.md:49 (boot 组)
_expect_exclusive("①boot", "菜单")
_expect_adventure_seal("①boot", false)
if main.ui.modals.menu.debug_overlay != null and main.ui.modals.menu.debug_overlay.visible:
@@ -119,7 +119,7 @@ func _step_boot_menu() -> void:
# ---- ② 菜单 → 营地(真实按钮信号路径)----
func _step_menu_to_camp() -> void:
# oracle: design/ux/camp.md:10 (菜单→营地组)
# 判据出处: 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/ux/camp.md:22 (营地→菜单组)
# 判据出处: design/ux/camp.md:22 (营地→菜单组)
if main.ui.modals.camp.scene == null:
_problems.append("③营地→菜单:营地场景根句柄缺失〔句柄子容器批 V1〕")
return
@@ -147,7 +147,7 @@ func _step_camp_back_to_menu() -> void:
# ---- 选档界面开合(卡③ sl10):函数直调开层(「进入存档」钮归卡② mn3)→屏级互斥采样→
# 返回钮真实信号路径关闭→ESC 语境不响应(卡⑤ 契约 2 范围外——零实现即断言)。----
func _step_save_select_open_back() -> void:
# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (选档开合组)
# 判据出处: design/gdd/systems/表现与支撑/存档序列化.md:61 (选档开合组)
ProbeUiUtil.isolate_save(main, "smoke_screen_flow_savesel", false, false, true)
main.save_manager.slots.attach_slot_template("user://smoke_screen_flow_savesel_slot_%d.json")
var esb: Button = ProbeUiUtil.find_button(main.ui.screens.start_overlay, "进入存档", "prefix")
@@ -194,7 +194,7 @@ func seals_check_single(f: Dictionary) -> bool:
func _step_menu_to_adventure() -> void:
# oracle: design/ux/level-map.md:11 (菜单→冒险组)
# 判据出处: 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/ux/battle.md:57 (冒险内切换组)
# 判据出处: 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/ux/level-map.md:42 (放弃→终局组)
# 判据出处: 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/ux/settlement-summary.md:24 (终局→菜单组)
# 判据出处: 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/ux/camp.md:45 (Esc 段组)
# 判据出处: 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/ux/camp.md:45 (窗壳 V1–V8 组)
# 判据出处: 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/ux/camp.md:45 (清窗组)
# 判据出处: design/ux/camp.md:45 (清窗组)
for k: StringName in [&"forge", &"chest", &"dummy", &"board"]:
var w: Control = views[k]
if not w.visible:
+11 -11
View File
@@ -46,7 +46,7 @@ func _ready() -> void:
await get_tree().process_frame
# ① 菜单(启动态)——无帮助文腿(玩法速览牌不在画面,checker 无对应断言段);
# 无副标题件,菜单文字锚只剩标题
# oracle: design/engineering/code-conventions.md:54 模态覆盖层层级规则——boot 态顶层=start_overlay、菜单文字在屏(菜单组)
# 判据出处: design/engineering/code-conventions.md:54 模态覆盖层层级规则——boot 态顶层=start_overlay、菜单文字在屏(菜单组)
# 自检一:boot 态顶层可见全屏层=start_overlay(内容层树遍历——屏壳是容器不当候选:
# camp 壳 boot 态 vis=true 但成员恒隐,裸树遍历自顶向下会先命中透明壳)
var top: Node = null
@@ -83,7 +83,7 @@ func _ready() -> void:
_meta[String(pair[0])] = ProbeUiUtil.label_rect(main.ui.screens.start_overlay, String(pair[1]))
await _snap("menu")
# ①b 设置面(settingsbtn 真实信号路径,开合同款真实钮)
# oracle: design/gdd/systems/表现与支撑/设置与本地化.md:95 最小设置面已兑现:音量/减动效/全屏/退出,载体=独立设置存储+设置界面视图(设置组)
# 判据出处: design/gdd/systems/表现与支撑/设置与本地化.md:95 最小设置面已兑现:音量/减动效/全屏/退出,载体=独立设置存储+设置界面视图(设置组)
var setbtn: Button = ProbeUiUtil.find_button(main.ui.screens.start_overlay, "设置", "prefix")
if setbtn == null:
_problems.append("设置入口缺失(开始层 settingsbtn)")
@@ -103,7 +103,7 @@ func _ready() -> void:
ProbeUiUtil.push_click(main, set_close)
await get_tree().process_frame
# ①c 生涯页(函数直调中间态——卡② mn10:careerbtn 已撤,卡④ 笔记本改挂前无 UI 路径)
# oracle: design/ux/camp.md:45 生涯页与四子界面同壳同形态(生涯组)
# 判据出处: 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/ux/camp.md:16 营地=可点击场景图(七物件命中区表——场景图形态组)
# 判据出处: 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/ux/camp.md:22 篝火=离开营地回主菜单、必须二次确认(篝心动线组)
# 判据出处: design/ux/camp.md:22 篝火=离开营地回主菜单、必须二次确认(篝心动线组)
await get_tree().process_frame
# ③ 关卡画面(开始冒险→停点出图——入口停点语义;函数直调中间态 mn10)
# oracle: design/ux/level-map.md:11 入口停点候选数=入口个数(关卡画面出图组)
# 判据出处: 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/ux/level-map.md:77 GP 常驻徽章=GP 图标+数字(冒险钱包读数;写路径回执后重查)
# 判据出处: 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 徽章不可见(关卡画面顶栏)")
@@ -150,7 +150,7 @@ func _ready() -> void:
if gp_label.text != expect:
_problems.append("GP 徽章读数不符: %s != %s" % [gp_label.text, expect])
# ④ 战斗(节点一——candidate_rect 命中矩形真点选路提交,ui_click_probe 同款)
# oracle: design/ux/battle.md:122 B 区构建:站桩/明细带/轮次指示(战斗出图与几何锚组)
# 判据出处: design/ux/battle.md:122 B 区构建:站桩/明细带/轮次指示(战斗出图与几何锚组)
if main.ui.modals.run.level_screen != null and main.ui.modals.run.level_screen.visible:
ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
for i in 10:
@@ -162,14 +162,14 @@ func _ready() -> void:
_meta["stage_view"] = _node_rect(main.ui.battle.stage)
_meta["board_zone"] = _node_rect(main.ui.battle.board_zone)
# draw calls 真机测量(基线 270→目标 <200;窗口化渲染帧读数,
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264 同屏 draw calls < 200(战斗屏性能预算组)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:264 同屏 draw calls < 200(战斗屏性能预算组)
# 取值时机=战斗截图同帧后一帧——呈现终态满负荷)
await get_tree().process_frame
var dc := int(Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME))
_meta["battle_draw_calls"] = dc
print("[shot_probe] battle draw_calls=%d(基线 270/目标 <200)" % dc)
# ⑤ 状态面板(战斗屏状态角标真实信号路径;层在战斗屏之上开合)
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:277 模态打开遮挡或输入锁定、关闭逐层恢复(状态面板组)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:277 模态打开遮挡或输入锁定、关闭逐层恢复(状态面板组)
var pb: Button = main.ui.battle.board_zone.status_badge
if pb == null or not pb.visible or pb.disabled:
_problems.append("状态面板入口角标缺失/置灰(战斗屏 board_zone)")
@@ -211,7 +211,7 @@ func _ready() -> void:
func _snap(key: String) -> void:
# oracle: design/engineering/test-conventions.md:84 golden 测试项扩项——截图采集清单扩项、实跑截图冻结(截图采集组)
# 判据出处: design/engineering/test-conventions.md:84 golden 测试项扩项——截图采集清单扩项、实跑截图冻结(截图采集组)
await RenderingServer.frame_post_draw
await RenderingServer.frame_post_draw
var img: Image = get_viewport().get_texture().get_image()
+1 -1
View File
@@ -13,7 +13,7 @@ var _problems: Array = []
func _ready() -> void:
# oracle: design/ux/battle.md:122 (明细带挖掘期收起——T17 视觉扩展时序组)
# 判据出处: design/ux/battle.md:122 (明细带挖掘期收起——T17 视觉扩展时序组)
await get_tree().process_frame
main = load("res://presentation/main.tscn").instantiate()
add_child(main)
+1 -1
View File
@@ -30,7 +30,7 @@ var _hero_pos := Vector2i.ZERO
func _ready() -> void:
# oracle: design/engineering/test-conventions.md:84 (微光截图取证——渲染闸采集组)
# 判据出处: design/engineering/test-conventions.md:84 (微光截图取证——渲染闸采集组)
if DisplayServer.get_name() == "headless":
print("[type_glow_shot] FAIL — 本探针必须窗口化运行(headless 无渲染面,截图必空)")
get_tree().quit(1)
+364 -39
View File
@@ -42,6 +42,9 @@ var _locktest_cell := Vector2i(-9, -9)
var _locktest_frames := 0
var _stoptest_state := 0 # 停手钮定型化覆盖段状态机(0=待命 1=已点击待核效 2=完结)——每 run 一次
var _stoptest_frames := 0
var _tested_node_continue := false # 迭代 55 B5 扩面——节点继续钮真输入一次性段
var _tested_curio_refresh := false # 迭代 55 B5 扩面——三选一刷新钮真输入一次性段
var _tested_shop_buy := false # 迭代 55 B5 扩面——商店购买+刷新钮真输入一次性段
func _ready() -> void:
@@ -61,7 +64,7 @@ func _ready() -> void:
if main.ui.modals.menu.debug_overlay.visible:
_problems.append("调试浮窗 boot 自动开(菜单优先失守)")
# ---- 营地购买合成点击路径(选档进档开层→购买→关闭全走真实按钮信号路径;
# oracle: design/gdd/systems/跨局循环/天赋树.md:198 (营地购买段——单币购买只扣钥石组)
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:198 (营地购买段——单币购买只扣钥石组)
# 隔离存档换注 main.meta 保确定性(禁碰真实档 user://save.json);陈旧档段首清理)----
for p0 in ["user://smoke_click_probe_reset.json", "user://smoke_click_probe_reset.json.tmp"]:
if FileAccess.file_exists(p0):
@@ -139,6 +142,47 @@ func _ready() -> void:
iso_fk.load_from_disk()
if String(iso_fk.meta_state.tree_state().fork_sides.get("fg_posture", "")) != "b":
_problems.append("Fork 切态未持久(reload 后激活态 != b)〔句柄子容器批 V1〕")
# ---- 树面高档钮扩面段(迭代 55 B5——购买0/20/40/60 档经调试注钥后真点+先手压制 A 侧回切)----
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:198 (营地购买段——购买面真输入扩面组)
iso_c.meta_state._add_keystone(9999)
main.ui.modals.camp.tree.refresh_camp(main._camp_data())
await get_tree().process_frame
for target_id in ["forge_workshop", "att_socket2", "att_socket3"]:
var hi_btn: Button = null
for card in main.ui.modals.camp.tree._tree_buy_btns:
if String(card.node_id) == target_id:
hi_btn = card.btn
if hi_btn == null:
_problems.append("树面 %s 购买钮缺失(高档扩面驱动中断)〔QA余 V13〕" % target_id)
elif hi_btn.disabled:
_problems.append("树面 %s 购买钮置灰(注钥后仍不可达——前置链失守)〔QA余 V13〕" % target_id)
else:
var price_hi: int = int(main.db.crossrun.meta_tree.row_by_id(target_id).get("price", 0))
var bal_hi: int = iso_c.meta_state.keystone_balance()
await ProbeUiUtil.scroll_button_into_view(main, main.ui.modals.camp.tree, hi_btn)
ProbeUiUtil.push_click(main, hi_btn) # 真点高档购买(forge_workshop=购买 0 钥石档)
await get_tree().process_frame
main.ui.modals.camp.tree.refresh_camp(main._camp_data())
await get_tree().process_frame
if int(iso_c.meta_state.keystone_balance()) != bal_hi - price_hi:
_problems.append("树面 %s 购买未出账(%d→%d 应 −%d)〔QA余 V13〕" % [target_id, bal_hi, iso_c.meta_state.keystone_balance(), price_hi])
if int(iso_c.meta_state.tree_state().nodes.get(target_id, 0)) != 1:
_problems.append("树面 %s 购买落点失守(节点未点亮)〔QA余 V13〕" % target_id)
# 先手压制 A 侧回切(先手压制=fg_posture A 侧名——购买默认 A 后既有段切至 B,A 侧钮零真输入命中)
var side_a: Button = null
for fk in main.ui.modals.camp.tree._tree_fork_btns:
if String(fk.group) == "fg_posture" and String(fk.side) == "a":
side_a = fk.btn
if side_a == null:
_problems.append("Fork A 侧切换钮缺失(先手压制回切驱动中断)〔QA余 V13〕")
else:
await ProbeUiUtil.scroll_button_into_view(main, main.ui.modals.camp.tree, side_a)
ProbeUiUtil.push_click(main, side_a) # 真点先手压制(B→A 回切)
await get_tree().process_frame
var iso_fk2 = load("res://core/save_manager.gd").new("user://smoke_click_probe_camp_slot_1.json")
iso_fk2.load_from_disk()
if String(iso_fk2.meta_state.tree_state().fork_sides.get("fg_posture", "")) != "a":
_problems.append("先手压制回切未持久(reload 后激活态 != a)〔QA余 V13〕")
# ---- 营地灰字取色断言(迭代36 wc4——四子界面拆分后按视图分扫;卡④ T5 迁移):
# 图鉴锁文→codex 视图(锚随形态订正去『柜台』);秘宝行未解锁/同调形态/档位/刷新行→treasure 视图
# _workshop_holder 子树。font_color 逐处 == STONE_DEEP;树链段已闭树界面。
@@ -193,6 +237,116 @@ func _ready() -> void:
else:
ProbeUiUtil.push_click(main, treasure_back) # 真点返回(closed 同源钮)
await get_tree().process_frame
# ---- 秘宝架扩面段(迭代 55 B5——购买10/购买25/装配/升级30 真点;钥匙门先经树购 ks_workshop 点亮)----
# 判据出处: design/gdd/systems/跨局循环/秘宝.md:246 (秘宝匣柜台——购买/装配/升级真输入扩面组)
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"dummy").get_center()) # 木桩→树界面
await get_tree().process_frame
var ks_btn: Button = null
for card in main.ui.modals.camp.tree._tree_buy_btns:
if String(card.node_id) == "ks_workshop":
ks_btn = card.btn
if ks_btn == null:
_problems.append("树面 ks_workshop 购买钮缺失(秘宝架扩面前置中断)〔QA余 V13〕")
else:
await ProbeUiUtil.scroll_button_into_view(main, main.ui.modals.camp.tree, ks_btn)
ProbeUiUtil.push_click(main, ks_btn) # 真点(free 行——钥匙门点亮)
await get_tree().process_frame
var ws_tree_back: Button = ProbeUiUtil.find_button(main.ui.modals.camp.tree, "返回营地")
if ws_tree_back != null:
ProbeUiUtil.push_click(main, ws_tree_back) # 真点返回营地
await get_tree().process_frame
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"chest").get_center()) # 宝箱→秘宝匣(已点亮态)
await get_tree().process_frame
var tview = main.ui.modals.camp.treasure
iso_c.meta_state._add_keystone(9999)
tview.refresh_camp(main._camp_data())
await get_tree().process_frame
var buy10: Button = null
var buy25: Button = null
for c in tview.find_children("*", "Button", true, false):
var cb := c as Button
if cb.text.begins_with("购买 10") and not cb.disabled and buy10 == null:
buy10 = cb
if cb.text.begins_with("购买 25") and not cb.disabled and buy25 == null:
buy25 = cb
if buy10 == null:
_problems.append("秘宝架「购买 10」钮缺失或置灰(真输入可达性失守)〔QA余 V13〕")
else:
var bal10: int = iso_c.meta_state.keystone_balance()
await ProbeUiUtil.scroll_button_into_view(main, tview, buy10)
ProbeUiUtil.push_click(main, buy10) # 真点购买 10
await get_tree().process_frame
if int(iso_c.meta_state.keystone_balance()) != bal10 - 10:
_problems.append("秘宝购买 10 未出账(%d→%d 应 −10)〔QA余 V13〕" % [bal10, iso_c.meta_state.keystone_balance()])
if buy25 == null:
_problems.append("秘宝架「购买 25」钮缺失或置灰(真输入可达性失守)〔QA余 V13〕")
else:
var bal25: int = iso_c.meta_state.keystone_balance()
await ProbeUiUtil.scroll_button_into_view(main, tview, buy25)
ProbeUiUtil.push_click(main, buy25) # 真点购买 25
await get_tree().process_frame
if int(iso_c.meta_state.keystone_balance()) != bal25 - 25:
_problems.append("秘宝购买 25 未出账(%d→%d 应 −25)〔QA余 V13〕" % [bal25, iso_c.meta_state.keystone_balance()])
# 装配/升级段前重开视图(_refresh_attune_segment 的 unlocked 快照仅随 rebuild 刷新——
# 购后不重开则新解锁件的装配钮恒禁用;重开=全量重建,快照与解锁集对齐)
var ws_back_mid: Button = ProbeUiUtil.find_button(main.ui.modals.camp.treasure, "返回营地")
if ws_back_mid != null:
ProbeUiUtil.push_click(main, ws_back_mid)
await get_tree().process_frame
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"chest").get_center())
await get_tree().process_frame
tview = main.ui.modals.camp.treasure
var attune_entry: Dictionary = {}
for a in tview._attune_btns:
if not (a["btn"] as Button).disabled:
attune_entry = a
break
if attune_entry.is_empty():
_problems.append("秘宝架「装配」钮缺失或全置灰(解锁后应可装)〔QA余 V13〕")
else:
await ProbeUiUtil.scroll_button_into_view(main, tview, attune_entry["btn"])
ProbeUiUtil.push_click(main, attune_entry["btn"]) # 真点装配(toggle_attunement 写口)
await get_tree().process_frame
if not (iso_c.meta_state.attune_state().equipped as Array).has(String(attune_entry["item_id"])):
_problems.append("秘宝装配落点失守(%s 未入装配集)〔QA余 V13〕" % String(attune_entry["item_id"]))
if tview._refresh_btn == null or tview._refresh_btn.disabled:
_problems.append("秘宝架「升级」钮缺失或置灰(Y 刷新升级行可达性失守)〔QA余 V13〕")
else:
var bal_y: int = iso_c.meta_state.keystone_balance()
var ylvl0: int = int(iso_c.meta_state.attune_state().refresh_level)
await ProbeUiUtil.scroll_button_into_view(main, tview, tview._refresh_btn)
ProbeUiUtil.push_click(main, tview._refresh_btn) # 真点升级(attune_refresh 同账写口)
await get_tree().process_frame
if int(iso_c.meta_state.attune_state().refresh_level) != ylvl0 + 1:
_problems.append("秘宝升级落点失守(refresh_level %d→%d)〔QA余 V13〕" % [ylvl0, int(iso_c.meta_state.attune_state().refresh_level)])
if int(iso_c.meta_state.keystone_balance()) >= bal_y:
_problems.append("秘宝升级未出账(%d→%d)〔QA余 V13〕" % [bal_y, iso_c.meta_state.keystone_balance()])
var ws_back2: Button = ProbeUiUtil.find_button(main.ui.modals.camp.treasure, "返回营地")
if ws_back2 != null:
ProbeUiUtil.push_click(main, ws_back2) # 真点返回(关闭后交还布告板段)
await get_tree().process_frame
# 树面购买 30 档(ks_refresh——前置 ks_workshop 已随秘宝段点亮;同账路由购 L2 扣 60)
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"dummy").get_center())
await get_tree().process_frame
var ks30_btn: Button = null
for card in main.ui.modals.camp.tree._tree_buy_btns:
if String(card.node_id) == "ks_refresh":
ks30_btn = card.btn
if ks30_btn == null:
_problems.append("树面 ks_refresh 购买钮缺失(30 档扩面驱动中断)〔QA余 V13〕")
elif ks30_btn.disabled:
_problems.append("树面 ks_refresh 购买钮置灰(ks_workshop 点亮后仍不可达)〔QA余 V13〕")
else:
var bal30: int = iso_c.meta_state.keystone_balance()
await ProbeUiUtil.scroll_button_into_view(main, main.ui.modals.camp.tree, ks30_btn)
ProbeUiUtil.push_click(main, ks30_btn) # 真点购买 30(同账路由——L2 档扣 60)
await get_tree().process_frame
if int(iso_c.meta_state.keystone_balance()) != bal30 - 60:
_problems.append("树面 ks_refresh 购买未出账(%d→%d 应 −60)〔QA余 V13〕" % [bal30, iso_c.meta_state.keystone_balance()])
var tree_back3: Button = ProbeUiUtil.find_button(main.ui.modals.camp.tree, "返回营地")
if tree_back3 != null:
ProbeUiUtil.push_click(main, tree_back3)
await get_tree().process_frame
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"board").get_center()) # 布告板→图鉴占位(锁文宿主)
await get_tree().process_frame
var codex_labels: Array = []
@@ -209,12 +363,44 @@ func _ready() -> void:
else:
ProbeUiUtil.push_click(main, codex_back) # 真点返回(closed 同源钮)
await get_tree().process_frame
# ---- 营地 Esc 浮窗设置动线(迭代 55 B5——Esc 真键唤起浮窗→设置钮真点开设置层)----
# 判据出处: design/ux/camp.md:45 (Esc 段组——三出口真输入扩面)
var esc_ev := InputEventKey.new()
esc_ev.keycode = KEY_ESCAPE
esc_ev.physical_keycode = KEY_ESCAPE
esc_ev.pressed = true
main.get_viewport().push_input(esc_ev, true) # 真键(营地根态——screen_flow 同款合成路径)
await get_tree().process_frame
if not main.ui.modals.camp.esc_overlay.visible:
_problems.append("营地 Esc 真键未唤起浮窗(扩面驱动中断)〔QA余 V13〕")
else:
var esc_settings: Button = main.ui.modals.camp.esc_overlay._settings_btn
if esc_settings == null:
_problems.append("Esc 浮窗「设置」钮缺失〔QA余 V13〕")
else:
ProbeUiUtil.push_click(main, esc_settings) # 真点设置(settings_requested 三出口之一)
await get_tree().process_frame
if not main.ui.modals.menu.settings_overlay.visible:
_problems.append("Esc 浮窗「设置」真点击未开设置层(落点判定失守)〔QA余 V13〕")
var esc_set_close: Button = ProbeUiUtil.find_button(main.ui.modals.menu.settings_overlay, "返回", "prefix")
if esc_set_close != null:
ProbeUiUtil.push_click(main, esc_set_close) # 真点返回收设置层
await get_tree().process_frame
if main.ui.modals.camp.esc_overlay.visible:
var esc_ev2 := InputEventKey.new()
esc_ev2.keycode = KEY_ESCAPE
esc_ev2.physical_keycode = KEY_ESCAPE
esc_ev2.pressed = true
main.get_viewport().push_input(esc_ev2, true) # 再按 Esc 关浮窗(同键单义切换)
await get_tree().process_frame
if main.ui.modals.camp.esc_overlay.visible:
_problems.append("再按 Esc 未关浮窗(扩面收口失守)〔QA余 V13〕")
main._on_camp_closed() # 关层收闭(营地与选档层同收+开始层恢复——卡④ T3)
await get_tree().process_frame
if main.ui.modals.camp.scene.visible or not main.ui.screens.start_overlay.visible:
_problems.append("营地关闭后未恢复开始层〔句柄子容器批 V1〕")
# ---- 选档界面行为走查(迭代 33 卡③):sl5 空槽新档链/sl6 有档槽进档链/sl7 删档二次确认双路径
# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (选档走查段——激活态建立组)
# 判据出处: design/gdd/systems/表现与支撑/存档序列化.md:61 (选档走查段——激活态建立组)
# /sl8 消费方分派单口/sl12 动线序不回闪。开层=函数直调(「进入存档」钮归卡② mn3——中间态在案);
# 隔离=attach_slot_template 注入命名空间,夹具按槽直写(卡① 注入口)。----
var csel_tpl := "user://smoke_click_probe_savesel_slot_%d.json"
@@ -318,7 +504,7 @@ func _ready() -> void:
_problems.append("[savesel] 关闭后层未隐藏或开始层未恢复(模态栈单口失守)")
# ---- 设置面段(隔离 store 换注禁碰真实 user://settings.cfg;真实按钮信号路径)----
# oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4 (设置面段——最小设置面组)
# 判据出处: design/gdd/systems/表现与支撑/设置与本地化.md:4 (设置面段——最小设置面组)
for p0 in ["user://smoke_click_probe_settings.cfg"]:
if FileAccess.file_exists(p0):
DirAccess.remove_absolute(p0)
@@ -341,7 +527,7 @@ func _ready() -> void:
if motion_btn == null:
_problems.append("设置面减动效开关缺失")
else:
motion_btn.button_pressed = false # off=快速(减动效开)
ProbeUiUtil.push_click(main, motion_btn) # 真点拨杆(迭代 55 B5 扩面——原 button_pressed 直改绕过真输入命中面;off=快速)
await get_tree().process_frame
if UiKit.motion_scale != 0.5:
_problems.append("减动效开未生效(motion_scale != 0.5)")
@@ -349,10 +535,24 @@ func _ready() -> void:
chk.load_settings()
if not chk.reduced_motion:
_problems.append("减动效开未持久化(设置独立存储失守——卡④ AC⑤)")
motion_btn.button_pressed = true # on=完整回常态
ProbeUiUtil.push_click(main, motion_btn) # 真点回完整(on=完整)
await get_tree().process_frame
if UiKit.motion_scale != 1.0:
_problems.append("减动效关未恢复(motion_scale != 1.0)")
# 全屏拨杆真输入段(迭代 55 B5 扩面——settings 两枚 switch_toggle 其二;store 态断言为准)
var fs_btn: Button = main.ui.modals.menu.settings_overlay._fs_btn
if fs_btn == null:
_problems.append("设置面全屏拨杆缺失〔QA余 V13〕")
else:
var fs0: bool = iso_s.fullscreen
ProbeUiUtil.push_click(main, fs_btn) # 真点全屏拨杆
await get_tree().process_frame
if bool(iso_s.fullscreen) == fs0:
_problems.append("全屏拨杆真点击未翻转 store 态(%s→%s)〔QA余 V13〕" % [str(fs0), str(iso_s.fullscreen)])
ProbeUiUtil.push_click(main, fs_btn) # 真点复原
await get_tree().process_frame
if bool(iso_s.fullscreen) != fs0:
_problems.append("全屏拨杆复原失守〔QA余 V13〕")
# 音量实时生效(卡④ AC①):滑杆 30% → master bus db 与 store 双读
var vol: HSlider = main.ui.modals.menu.settings_overlay._volume
vol.value = 30
@@ -385,7 +585,7 @@ func _ready() -> void:
if ProbeUiUtil.find_button(main.ui.modals.menu.settings_overlay, "退出游戏", "prefix") != null:
_problems.append("设置面仍含退出游戏按钮(二轮 D1——只在一级菜单出现)")
# ---- 生涯页段(入口点击链+关闭回返,真实按钮信号路径;本页只读零写,
# oracle: design/ux/camp.md:45 (生涯页段——同壳同形态组)
# 判据出处: design/ux/camp.md:45 (生涯页段——同壳同形态组)
# 值断言归冒烟投影段与 ui_struct 结构族,此处只验开合路径与零跳转)----
# 生涯页驱动=函数直调中间态(卡② mn10——careerbtn 已撤,卡④ 笔记本改挂前无 UI 路径)
if true:
@@ -412,7 +612,7 @@ func _ready() -> void:
_problems.append("生涯页关闭后未恢复开始层")
# 换回全新零档(营地购买态不泄入后续冒险段——确定性;档案残留下次段首清理兜底)
ProbeUiUtil.isolate_save(main, "smoke_click_probe_reset", true, false, true) # 零档换注(继续上局钮已随三入口化撤出——卡② mn8)
main.ui.modals.menu.debug_overlay.set_sliders(400, 200) # 高加成保 L1 可完成——L2 岔路选路段只能经冒险推进到达(单关直入口退役)
main.ui.modals.menu.debug_overlay.set_sliders(2000, 2000) # 高加成保 L1 可完成——L2 岔路选路段只能经冒险推进到达(单关直入口退役)
main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机,行为走查确定性禁依赖默认分支)
var t1 := Time.get_ticks_msec()
main._start_adventure_from_menu()
@@ -451,7 +651,17 @@ func _ready() -> void:
action_budget -= 1
await get_tree().process_frame
if main.ui.modals.run.node_overlay.visible:
ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn)
# 节点继续一次性落点断言(迭代 55 B5 扩面——休息停点真输入继续,落点=overlay 关闭回到战斗屏)
if not _tested_node_continue:
_tested_node_continue = true
if not main.ui.flow.locks_input():
_problems.append("节点信息浮窗打开时输入未锁定〔QA余 V13〕")
ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn) # 真点「继续」
await get_tree().process_frame
if main.ui.modals.run.node_overlay.visible:
_problems.append("节点「继续」真点击后浮窗未关(落点判定失守)〔QA余 V13〕")
else:
ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn)
_seals_placed.clear()
continue
if main.ui.modals.run.curio_overlay.visible:
@@ -475,6 +685,17 @@ func _ready() -> void:
_problems.append("奖励节点战报文案未换代(卡⑤ wr2——旧话残留或新话缺失)")
if not main.ui.flow.locks_input():
_problems.append("三选一停点打开时输入未锁定")
# 三选一刷新钮一次性真输入段(迭代 55 B5 扩面——重抽意图经 refresh_requested 信号走组合根
# on_refresh;落点=停点仍开且候选重投喂,零崩零关)
if not _tested_curio_refresh and main.ui.modals.run.curio_overlay._refresh_btn.visible \
and not main.ui.modals.run.curio_overlay._refresh_btn.disabled:
_tested_curio_refresh = true
var cards_before: int = (main.ui.modals.run.curio_overlay._bcards as Array).size()
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._refresh_btn) # 真点刷新图标钮
await get_tree().process_frame
if not main.ui.modals.run.curio_overlay.visible \
or (main.ui.modals.run.curio_overlay._bcards as Array).size() != cards_before:
_problems.append("三选一刷新钮真点击后停点未保持重投喂形态(落点判定失守)〔QA余 V13〕")
if main.ui.modals.run.curio_overlay._continue_btn.visible:
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():
@@ -498,6 +719,39 @@ func _ready() -> void:
if shop_leave == null:
_problems.append("商店「离开商店」钮缺失(真输入化驱动中断)")
else:
# 商店购买+刷新一次性真输入段(迭代 55 B5 扩面——购买写口经 buy_requested 信号,
# 刷新经 refresh_requested→on_shop_refresh MC-1;测试通道注 GP 保可达,行为序列零依赖)
if not _tested_shop_buy:
_tested_shop_buy = true
var rnr_shop = main.run_scope.runner
if rnr_shop != null and int(rnr_shop.gp) < 500:
rnr_shop.gp = 500 # 测试通道:注 GP 使购买与付费刷新可达(探针行为序列不依赖本注记)
await get_tree().process_frame
var buy_btn: Button = null
for c in main.ui.modals.menu.shop_overlay.find_children("*", "Button", true, false):
var cb := c as Button
if cb.text.begins_with("购买") and not cb.disabled:
buy_btn = cb
break
if buy_btn == null:
_problems.append("商店「购买」钮缺失或全置灰(真输入可达性失守)〔QA余 V13〕")
else:
var gp_before: int = int(rnr_shop.gp)
ProbeUiUtil.push_click(main, buy_btn) # 真点购买
await get_tree().process_frame
if int(rnr_shop.gp) >= gp_before:
_problems.append("商店购买真点击未出账(GP %d→%d)〔QA余 V13〕" % [gp_before, int(rnr_shop.gp)])
var shop_refresh: Button = main.ui.modals.menu.shop_overlay._refresh_btn
if shop_refresh != null and not shop_refresh.disabled:
var gp_r: int = int(rnr_shop.gp)
ProbeUiUtil.push_click(main, shop_refresh) # 真点刷新(重抽货架)
await get_tree().process_frame
if not main.ui.modals.menu.shop_overlay.visible:
_problems.append("商店刷新真点击后覆盖层异常关闭(落点判定失守)〔QA余 V13〕")
if int(rnr_shop.gp) > gp_r:
_problems.append("商店刷新未出账反涨(GP %d→%d)" % [gp_r, int(rnr_shop.gp)])
else:
_problems.append("商店刷新钮缺失或置灰(MC-1 投喂可达性失守)〔QA余 V13〕")
ProbeUiUtil.push_click(main, shop_leave) # 真点离店(leave_pressed 同源钮)放行选路
_seals_placed.clear()
continue
@@ -534,13 +788,21 @@ func _ready() -> void:
_problems.append("地图屏加成帽未换代为「属性值提升」(wa2)")
# 选路定向(卡⑤ d4——wa4 转正的必达前提):候选含奖励节点优先选(真实
# press_candidate 命中路径零变化,只改索引策略);无奖励候选回退交替(岔路使用率观测面保留)
# 迭代 55 B5 扩面——节点继续段未测时休息候选最优先(node_overlay 仅休息停点开启)
var pick := _route_clicks % 2
if main.run_scope.runner != null:
for nd in main.run_scope.runner.nodes():
var nd_id: int = main.run_scope.runner.node_id_of(nd)
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
if not _tested_node_continue:
for nd in main.run_scope.runner.nodes():
var nd_id_rest: int = main.run_scope.runner.node_id_of(nd)
if String(nd.get("type", "")) == "rest" and main.ui.modals.run.level_screen._cand_ids.has(nd_id_rest):
pick = main.ui.modals.run.level_screen._cand_ids.find(nd_id_rest)
break
if pick == _route_clicks % 2:
for nd in main.run_scope.runner.nodes():
var nd_id: int = main.run_scope.runner.node_id_of(nd)
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
ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(pick).get_center())
_route_clicks += 1
_seals_placed.clear()
@@ -640,7 +902,7 @@ func _ready() -> void:
_run_modal_probes_once()
_play_one_action()
# ---- 预算耗尽收口(T17 硬锁批适配):选件变强后 probe 可能活过预算不再战败——断点段前提
# oracle: design/ux/level-map.md:11 (主循环选路段——入口候选组)
# 判据出处: design/ux/level-map.md:11 (主循环选路段——入口候选组)
# (冒险终局面在案)须主动满足。收口驱动:任何停点层逐个推进(复用主循环分支语义)至
# level_screen 选路停点→真实放弃二击→终局面。战斗中则继续 autoplay 打完。
if not _done and not main.ui.modals.run.end_overlay.is_open():
@@ -689,7 +951,7 @@ func _ready() -> void:
_play_one_action()
# 排播期/Waiting 等待态=自然流逝(演播秒级、锁随队列空解除)
# T17 适配:战败或放弃二途皆达终局面(放弃=预算耗尽收口路径——见上),断点段前提不变。
# oracle: design/gdd/systems/表现与支撑/存档序列化.md:180 (断点段——断点域与续玩组)
# 判据出处: design/gdd/systems/表现与支撑/存档序列化.md:180 (断点段——断点域与续玩组)
await get_tree().process_frame
var bp_file := "user://smoke_click_probe_reset.json"
var iso_chk = load("res://core/save_manager.gd").new(bp_file)
@@ -720,8 +982,56 @@ func _ready() -> void:
# 卡④大门 T5 接收,两头登记禁静默删除;续玩钮已退役,主菜单确认链已摘 mn6)。
# ① 战斗收口断点写盘断言保留(上方——冒险流直调中间态 mn10)。
# ---- 迭代 55 B5 扩面收尾段(地图放弃动线——主循环流不定达放弃,独立新局确定性驱动)----
# 判据出处: design/ux/level-map.md:11 (主循环选路段——放弃二击真输入组)
ProbeUiUtil.isolate_save(main, "smoke_click_probe_expansion", true, false, true)
main.ui.modals.menu.debug_overlay.set_sliders(2000, 2000) # 高加成上限(与主循环同口径——层速推进保放弃停点快达)
main.ui.modals.menu.debug_overlay.seed_edit.text = "44"
main._start_adventure_from_menu()
await get_tree().process_frame
await get_tree().process_frame
var ab_guard := 4000
var ab_done := false
while ab_guard > 0 and not ab_done:
ab_guard -= 1
await get_tree().process_frame
if main.ui.modals.run.end_overlay.is_open():
break # 败退先至——放弃动线未达(问题行呈报)
if main.ui.modals.run.curio_overlay.visible:
if main.ui.modals.run.curio_overlay._continue_btn.visible:
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():
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:
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._continue_btn)
continue
if main.ui.modals.run.node_overlay.visible:
ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn)
continue
if main.ui.modals.run.level_screen.visible:
var ab_btn: Button = ProbeUiUtil.find_button(main.ui.modals.run.level_screen, "放弃", "prefix")
if ab_btn != null:
ProbeUiUtil.push_click(main, ab_btn) # 真点「放弃」一击(3s 确认窗)
await get_tree().process_frame
ProbeUiUtil.push_click(main, ab_btn) # 真点二击确认(窗内)
for i in 10:
await get_tree().process_frame
if main.ui.modals.run.end_overlay.is_open():
print("[click_probe] 放弃动线落点=终局面(地图放弃真输入)")
else:
_problems.append("放弃二击后未达终局面(落点判定失守)〔QA余 V13〕")
ab_done = true
if not ab_done:
_problems.append("扩面收尾段放弃动线未达(guard 耗尽或败退先至)〔QA余 V13〕")
# 终局面返回开始菜单(大门段菜单前提归位——句柄沿断点段同源 _btn,文本查找零命中实证)
if main.ui.modals.run.end_overlay.is_open() and main.ui.modals.run.end_overlay._btn != null:
ProbeUiUtil.push_click(main, main.ui.modals.run.end_overlay._btn) # 真点「返回」(_btn=继续/返回共用句柄——断点段同款)
await get_tree().process_frame
# ---- 大门两分支断言段(卡④ T5 接收——卡② 移交落点;V3 断言面)----
# oracle: design/ux/camp.md:16 (大门段——点大门先出确认组)
# 判据出处: design/ux/camp.md:16 (大门段——点大门先出确认组)
# 驱动=营地场景根点选信号(真实路由)→组合根 _on_camp_object_pressed→确认弹窗。
# 无断点=两出口(是/否);有断点=三出口(set_breakpoint 注入选路停点形——同下方开场行③先例)。
# 判据绑可观测提交面:点大门不直接进冒险(is_adventure 翻转只在 confirmed 后);cancelled 恒=
@@ -746,7 +1056,7 @@ func _ready() -> void:
await get_tree().process_frame
# R4 位次真点(营地交互修复批——位次与语义绑定钉):真点前消费方前置读(gate_new 在册)→
# 按类型定位按钮行 child(0) 全局矩形中心真点——位次若错接,此点命中「否」,下方主证(翻转)即红。
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:90 确认弹窗位次定值:推进在左、维持居右(R4 位次真点)
# 判据出处: 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)
@@ -761,7 +1071,7 @@ func _ready() -> void:
if String(main._confirm_consumer) != "":
_problems.append("R4 主证:确认后消费方未清零〔营地交互批 V3〕")
# 开局终态两钉(营地交互修复批——开始层隐藏收一处后的落点断言):
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:78 关卡开始 → 呈现节点图选路停点(开局终态两钉)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:78 关卡开始 → 呈现节点图选路停点(开局终态两钉)
# R1=确认后开始层不可见(被测改动特有提交面——修复前该句恒红=「开局被开始层盖住」缺陷本体);
# R2=关卡画面(选路停点)在位(开局终态呈现面,与既有 is_adventure 断言同批缺一即红)
if main.ui.screens.start_overlay.visible:
@@ -865,10 +1175,10 @@ func _ready() -> void:
# ---- 模态/tooltip/刷新链一次性覆盖(真实信号路径驱动) ----
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:277 (模态/tooltip 段——遮挡与输入锁组)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:277 (模态/tooltip 段——遮挡与输入锁组)
func _run_modal_probes_once() -> void:
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:277 (判定上报通道——验证函数组)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:277 (判定上报通道——验证函数组)
# ⓪ 玩家面文案真实入口断言(迭代36 卡A wa1——C 区两段头随战斗常驻)
if not _tested_copy and main.ui.battle.info_panel != null and main.ui.battle.info_panel.visible:
_tested_copy = true
@@ -906,7 +1216,15 @@ func _run_modal_probes_once() -> void:
", "|"))
if not legend_has_seal:
_problems.append("图例缺『封印』格态条目(格态图例基线)")
main.ui.modals.run.legend_overlay.close()
# 图例关闭走真输入关闭钮(迭代 55 B5 扩面——原 .close() 函数直调绕过关闭钮命中面;
# 本函数为主循环内同步调用(协程化会与主循环双分派竞态)——push_click 的 release
# 同帧触发关闭处理器,断言同帧读数,零 await)
var legend_close_btn: Button = ProbeUiUtil.find_button(main.ui.modals.run.legend_overlay, "关闭")
if legend_close_btn == null:
_problems.append("图例「关闭」钮缺失(真输入关闭路径驱动中断)〔QA余 V13〕")
main.ui.modals.run.legend_overlay.close()
else:
ProbeUiUtil.push_click(main, legend_close_btn) # 真点「关闭」收图例
if main.ui.modals.run.legend_overlay.is_open() or main.ui.flow.locks_input():
_problems.append("图例关闭后未恢复输入/可见性")
# ②/②b 明细回看模态无探针(M9/P-C7——冒险中回看归「生涯」,06 登记)
@@ -944,7 +1262,7 @@ func _run_modal_probes_once() -> void:
rnr.effective_ac(), int(rnr.gp)]
if String(main.ui.modals.run.status_panel._attrs_label.text) != attr_expect:
_problems.append("状态面板属性行投影失真(sv6②——期望 %s 实测 %s)" % [attr_expect, main.ui.modals.run.status_panel._attrs_label.text])
# ③ _status_data() 直调对拍(sv7——键集恰八键+curios/feats 独立 oracle)
# ③ _status_data() 直调对拍(sv7——键集恰八键+curios/feats 独立预期表)
var sd: Dictionary = main.status_data()
var keys := ["balance", "hero_hp", "max_hp", "atk", "ac", "gp", "curios", "feats"]
var keys_ok: bool = (sd as Dictionary).size() == 8
@@ -952,18 +1270,18 @@ func _run_modal_probes_once() -> void:
keys_ok = keys_ok and (sd as Dictionary).has(k)
if not keys_ok:
_problems.append("_status_data() 键集≠八键(sv7——实测 %s)" % str((sd as Dictionary).keys()))
var oracle: Array = []
var expected: Array = []
for rec in rnr.treasure_inventory.query_inventory(Callable()):
var rr: Dictionary = rec
oracle.append({"name": String(main.db.crossrun.treasure_items.row_by_id(String(rr.get("item_id", ""))).get("display_name", "")),
expected.append({"name": String(main.db.crossrun.treasure_items.row_by_id(String(rr.get("item_id", ""))).get("display_name", "")),
"used": bool(rr.get("used", false))})
var sd_curios: Array = (sd as Dictionary).get("curios", [])
var curios_ok: bool = (sd_curios as Array).size() == (oracle as Array).size()
var curios_ok: bool = (sd_curios as Array).size() == (expected as Array).size()
if curios_ok:
for i in (oracle as Array).size():
curios_ok = curios_ok and String((sd_curios[i] as Dictionary).get("name", "")) == String((oracle[i] as Dictionary).get("name", "")) and bool((sd_curios[i] as Dictionary).get("used", false)) == bool((oracle[i] as Dictionary).get("used", false))
for i in (expected as Array).size():
curios_ok = curios_ok and String((sd_curios[i] as Dictionary).get("name", "")) == String((expected[i] as Dictionary).get("name", "")) and bool((sd_curios[i] as Dictionary).get("used", false)) == bool((expected[i] as Dictionary).get("used", false))
if not curios_ok:
_problems.append("_status_data().curios 对拍独立 oracle 失守(sv7——实测 %s vs %s)" % [str(sd_curios), str(oracle)])
_problems.append("_status_data().curios 对拍独立预期表失守(sv7——实测 %s vs %s)" % [str(sd_curios), str(expected)])
var feats_expect: Array = load("res://presentation/hud_presenter.gd").feat_holdings_lines(main.db, rnr.feats_held())
if str((sd as Dictionary).get("feats", [])) != str(feats_expect):
_problems.append("_status_data().feats 对拍 feat_holdings_lines 失守(sv7)")
@@ -1001,6 +1319,13 @@ func _run_modal_probes_once() -> void:
_problems.append("状态槽 tooltip 未随移出隐藏")
break
# ④ 弃图刷新确认链(pattern 7 异位:确认 chips 与常规确认不同位 + 显式代价文案)
# 迭代 55 B5 扩面——确定性前置:RAP 不足时经 grant_reserve_ap 注足(测试通道——探针行为序列
# 不依赖本注记;零注记时本链只在停手转存积累 RAP≥8 的偶发轮触发=覆盖不稳定实证)
if not _tested_refresh_chain and main.run_scope.settlement != null \
and main.run_scope.settlement.action_economy.phase == "Active" \
and not main.run_scope.settlement.can_refresh() \
and int(main.run_scope.settlement.action_economy.rap) < 8:
main.run_scope.settlement.action_economy.grant_reserve_ap(8 - int(main.run_scope.settlement.action_economy.rap))
if not _tested_refresh_chain and main.ui.battle.board_zone.ap_bar.is_refresh_ready():
_tested_refresh_chain = true
var rap_before: int = int(main.run_scope.settlement.action_economy.rap)
@@ -1045,7 +1370,7 @@ func _labels_with_text(labels: Array, needle: String) -> Array:
func _play_one_action() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:236 (判定上报通道——验证函数组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:236 (判定上报通道——验证函数组)
var settlement = main.run_scope.settlement
if settlement == null:
return # 结算竞态防抖:battle_finished 与本轮动作调度同帧到达时 settlement 可能已被置空(基线既有噪声——加固)
@@ -1086,7 +1411,7 @@ func _play_one_action() -> void:
func _random_unknown_adjacent(minesweeper) -> Vector2i:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:236 (判定上报通道——验证函数组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:236 (判定上报通道——验证函数组)
var hero: Vector2i = minesweeper.hero_pos()
var cands: Array = []
for dy in range(-1, 2):
@@ -1102,13 +1427,13 @@ func _random_unknown_adjacent(minesweeper) -> Vector2i:
func _click_stop() -> void:
# oracle: design/gdd/systems/排雷循环/行动经济.md:262 (判定上报通道——验证函数组)
# 判据出处: design/gdd/systems/排雷循环/行动经济.md:262 (判定上报通道——验证函数组)
# 停手走真实点击路径(AP 条「停手」钮 → 组合根 settle_batch——排雷玩法 D1×6① 回归载体)
ProbeUiUtil.push_click(main, main.ui.battle.board_zone.ap_bar._btn_stop)
# ---- T1 D1:锁期/重放断言(每 run 一次,脚本化场景——演播期豁免的断言化承载) ----
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:236 (锁期/重放段——拒绝与锁组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:236 (锁期/重放段——拒绝与锁组)
# 锁期断言(排播队列活跃=锁面):注入封印击 → 直落被冻结不受理(封印未置)+ `_locked_click`
# 缓存命中且为本注入(「最后一次」语义——注入帧内无并发点击)。
# 重放断言(演播完边沿 pump_ended→_replay_locked_click 重入唯一动作通道):缓存清空 +
@@ -1116,7 +1441,7 @@ func _click_stop() -> void:
# 前提消失守卫:等重放期间出现停点层/战斗收场(注入时点恰逢终结演播)→ 静默弃测不计
# problem(封印动作不杀伤,正常局不触发;断言实现形态取舍在代拍白名单内)。
func _locktest_tick() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:236 (判定上报通道——验证函数组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:236 (判定上报通道——验证函数组)
if _locktest_state == 2:
return
var settlement = main.run_scope.settlement
@@ -1173,7 +1498,7 @@ func _locktest_tick() -> void:
func _locktest_pick_cell(minesweeper) -> Vector2i:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:236 (判定上报通道——验证函数组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:236 (判定上报通道——验证函数组)
var hero: Vector2i = minesweeper.hero_pos()
for dy in range(-1, 2):
for dx in range(-1, 2):
@@ -1191,7 +1516,7 @@ func _locktest_pick_cell(minesweeper) -> Vector2i:
# 断言两钉:①覆盖登记件停手键在场(零命中=驱动空转假绿,即红);②相位离开 Active(停手
# 收轮)。前提消失守卫:点击后战局收场(终结演播竞态)→ 静默弃测不计 problem(正常局不触发)。
func _stoptest_tick() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:167 (停手=玩家主动收轮——判定上报通道)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:167 (停手=玩家主动收轮——判定上报通道)
if _stoptest_state == 2:
return
var settlement = main.run_scope.settlement
@@ -1228,7 +1553,7 @@ func _stoptest_tick() -> void:
func _stoptest_assert_hit() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:167 (停手钮真输入命中登记——判定上报通道)
# 判据出处: 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):
@@ -1236,7 +1561,7 @@ func _stoptest_assert_hit() -> void:
func _first_hbox(node: Node) -> HBoxContainer:
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:90 (位次真点定位——R4 判据面辅助)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:90 (位次真点定位——R4 判据面辅助)
if node is HBoxContainer:
return node
for ch in node.get_children():
@@ -1247,7 +1572,7 @@ func _first_hbox(node: Node) -> HBoxContainer:
func _report_stall(st: Dictionary) -> void:
# oracle: design/engineering/test-conventions.md:30 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:30 (判定上报通道——验证函数组)
var why := ""
if st.phase == "Waiting":
why = "结算停滞:Waiting 相位无推进(结算应同步走完——窗口段已删除)"
@@ -1263,7 +1588,7 @@ func _report_stall(st: Dictionary) -> void:
func _dump_state() -> void:
# oracle: design/engineering/test-conventions.md:30 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:30 (判定上报通道——验证函数组)
var diag: Dictionary = main.run_scope.settlement.diagnostic_snapshot() # 诊断观测面(私有字段零直读)
print(" node_index=%s round=%s phase=%s ap=%s rap=%s" % [
str(main.run_scope.runner.node_index), str(main.run_scope.settlement.round_index),
+1 -1
View File
@@ -88,7 +88,7 @@ func _ratio(a: Color, b: Color) -> float:
func _ready() -> void:
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:281 (UI 文字对比度机检 ≥4.5:1 无区域例外——对比度组)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:281 (UI 文字对比度机检 ≥4.5:1 无区域例外——对比度组)
var problems: Array = []
var pairs := _pairs()
for p in pairs:
+12 -12
View File
@@ -242,7 +242,7 @@ func _ready() -> void:
_pre_fail("目标格选取失败 seal=%s dig=%s forbid=%s reveal=%s" % [seal_t, dig_t, forbid_t, reveal_t])
return
# ---- case① 悬停可挖域:motion → 悬停态到达(金框 + 十字光标)----
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:269 悬停光标三语义随目标切换、已开格路径预览、可挖域常驻微光(case①-③ 输入面)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:269 悬停光标三语义随目标切换、已开格路径预览、可挖域常驻微光(case①-③ 输入面)
_push_motion(_pos(bv, seal_t))
await get_tree().process_frame
if bv.hover_probe() != seal_t:
@@ -271,7 +271,7 @@ func _ready() -> void:
if bv.mouse_default_cursor_shape != Input.CURSOR_ARROW:
_problems.append("case③ 移动光标缺失(%d)" % bv.mouse_default_cursor_shape)
# ---- case④ 右键封印:seal_place 到达组合根 + 核心封印态落位 ----
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231 封印受理与印记随挖消失(case④/④b)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:231 封印受理与印记随挖消失(case④/④b)
_push_click(_pos(bv, seal_t), MOUSE_BUTTON_RIGHT)
await get_tree().process_frame
if not _has_action(seal_t, "seal_place"):
@@ -285,14 +285,14 @@ func _ready() -> void:
if seal_unit == null or not seal_unit.seal_on:
_problems.append("case④b 封印印记未下发(seal_on=%s)" % ["无单元" if seal_unit == null else str(seal_unit.seal_on)])
# ---- case⑤ 双击 chord:禁入格双左击(核心拒挖零副作用)→ chord_attempted ----
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:236 拒绝三源可辨、任何拒绝零副作用(case⑤ chord)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:236 拒绝三源可辨、任何拒绝零副作用(case⑤ chord)
_push_click(_pos(bv, forbid_t), MOUSE_BUTTON_LEFT)
_push_click(_pos(bv, forbid_t), MOUSE_BUTTON_LEFT)
await get_tree().process_frame
if _chords.is_empty() or _chords[_chords.size() - 1] != forbid_t:
_problems.append("case⑤ chord_attempted 未达(chords=%s)" % [str(_chords)])
# ---- case⑥ 左键挖:dig 到达组合根 + 核心揭格落位 ----
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231 挖掘受理与揭格落位(case⑥)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:231 挖掘受理与揭格落位(case⑥)
_push_click(_pos(bv, dig_t), MOUSE_BUTTON_LEFT)
await get_tree().process_frame
if not _has_action(dig_t, "dig"):
@@ -301,7 +301,7 @@ func _ready() -> void:
if not ms.is_revealed(dig_t):
_problems.append("case⑥ 核心揭格未落位(目标=%s)" % dig_t)
# ---- case⑦ 英雄精灵世界落位(回归闸):精灵在 SubViewport 世界内,
# oracle: design/ux/battle.md:154 主角站桩立绘带落影、贴脚与缩放档(case⑦-⑩ 呈现落位)
# 判据出处: design/ux/battle.md:154 主角站桩立绘带落影、贴脚与缩放档(case⑦-⑩ 呈现落位)
# 落位 = 格×步距 + 脚底锚(横=格中线/纵=格底往上 25% 线=HERO_GROUND_FRAC 自底边口径——「脚踩
# 格子 1/4 处·从下往上数」;禁带壳层 _svp_container.position 居中偏移——上批误用壳层
# cell_origin 实测右偏 ~7 格+偏离格心,实机截图用户报缺陷)。素材缺位树(hero==null,字形
@@ -368,7 +368,7 @@ func _ready() -> void:
if bv._world.hero.position.distance_to(expect10b) > 0.5:
_problems.append("case⑩ 还原后英雄未复位(pos=%s expect=%s)" % [str(bv._world.hero.position), str(expect10b)])
# ---- case⑪ 贴图缺位回退(字形「人」回归闸):texture 置 null + refresh → 英雄格数字 0 时字形
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264 棋盘格子与数字完整可见——贴图缺位回退字形保可读(case⑪)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:264 棋盘格子与数字完整可见——贴图缺位回退字形保可读(case⑪)
# 必须回退「人」(数字格仍显数字——回退判据与抑制判据同源);验毕还原纹理重绘。 ----
if bv._world.hero != null:
var hp11: Vector2i = ms.hero_pos()
@@ -385,7 +385,7 @@ func _ready() -> void:
bv._hero_sprite.tex_idle = tex_saved
bv.refresh()
# ---- case⑭ 变异角标(flat_damage 未开格视觉标记;resolver 注入面+落位+复位) ----
# oracle: design/gdd/systems/排雷循环/地形变异.md:154 呈现按 effect/trigger/placement 数据路由、零特判(case⑭ 角标)
# 判据出处: design/gdd/systems/排雷循环/地形变异.md:154 呈现按 effect/trigger/placement 数据路由、零特判(case⑭ 角标)
var mark_t: Vector2i = _pick_forbidden(bv, ms) # 任一未开格(禁入=未开非相邻)
if mark_t.x >= 0:
bv.set_variant_mark_resolver(func(_c): return "flat_damage")
@@ -399,7 +399,7 @@ func _ready() -> void:
else:
_problems.append("case⑭ 无未开格可验(板面异常)")
# ---- case⑮ 视野迷雾(三态呈态/半径圈/移动跟随/开关两态零扰动/提灯插值位;
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:268 视野迷雾:灯光区随移动跟随、开关两态结算逐位一致(case⑮)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:268 视野迷雾:灯光区随移动跟随、开关两态结算逐位一致(case⑮)
# 纯呈现零逻辑——迷雾开关切换前后 minesweeper 状态摘要逐位一致) ----
var fogp: Dictionary = bv.fog_params_probe()
if int(bv.FOG_RADIUS) != 8 or int(fogp["lantern"]) != 0 or not fogp["enabled"]:
@@ -495,7 +495,7 @@ func _ready() -> void:
# attune_param_sum 只读出口→set_fog_glow_bonus〕→视野 +4 生效与卸装复原。
# settlement.attunement 手动注入=模拟 begin_battle enc 注入后的运行态〔注入面本体归
# 冒烟+单测覆盖〕;不重启冒险——case⑯ 起既有板面继续用)
# oracle: design/gdd/systems/跨局循环/元进度.md:156 解锁可见:装配强化下一局生效(case⑮ 装配链)
# 判据出处: design/gdd/systems/跨局循环/元进度.md:156 解锁可见:装配强化下一局生效(case⑮ 装配链)
main.save_manager.meta_state.set_attunement_equipped(["wide_scout_band"])
var p_lantern: Dictionary = main.build_adventure_params(main.save_manager, main.meta)
if not (p_lantern.get("attunement", []) as Array).has("wide_scout_band"):
@@ -514,7 +514,7 @@ func _ready() -> void:
_problems.append("case⑮ 卸装后 dist==R+1 格未复原遮蔽")
main.save_manager.meta_state.set_attunement_equipped([])
# ---- case⑯ 状态面板输入锁语义(R3——开层锁棋盘、关层恢复无残留;真实 push_input
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:277 模态打开输入锁定、关闭逐层恢复(case⑯ R3)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:277 模态打开输入锁定、关闭逐层恢复(case⑯ R3)
# 路径:入口角标本体 press+release 真点击 → 状态面板开;锁期挖掘点击零穿透;「继续」关层后
# 同格挖掘重新到达——锁语义与既有模态同款,无状态残留。置于全 case 之尾:取格若需前沿移位
#(真实点击 move 扩大可挖域),其板面扰动不再波及任何既有断言——域恰 2 枚时 seal_t 带封印+
@@ -570,7 +570,7 @@ func _ready() -> void:
if not _has_action(status_dig, "dig") or not ms.is_revealed(status_dig):
_problems.append("case⑯ 关闭后挖掘未到达(恢复无残留验证失守)")
# ---- case⑰ 未开格类型微光(活板面走查:作用集恒等/开格灭/移动复亮
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:267 类型微光出圈即灭入圈即亮、已开与封锁格恒无微光(case⑰)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:267 类型微光出圈即灭入圈即亮、已开与封锁格恒无微光(case⑰)
# 与出圈灭/相位门控/开关两态零扰动。与冒烟段数据面断言分工:此处走真实点击路径) ----
if not bool(bv.type_glow_probe().get("enabled", false)):
_problems.append("case⑰ 微光默认态非开(生产默认开——Q4 丙案)")
@@ -759,7 +759,7 @@ func _push_release(pos: Vector2, btn: int) -> void:
func _pre_fail(why: String) -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
push_error("[ui_input_probe] FAIL — " + why)
print("[ui_input_probe] FAIL — " + why)
get_tree().quit(1)
+14 -14
View File
@@ -16,7 +16,7 @@ func _ready() -> void:
await get_tree().process_frame
var problems: Array = []
# ---- 放弃入口(台账 119):默认隐藏(非冒险模式无冒险可放弃——开始层/调试两层)----
# oracle: design/ux/level-map.md:59 放弃冒险入口=边缘动作、任意选路停点承载——非冒险模式无入口(默认隐藏)
# 判据出处: 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/ux/camp.md:45 五功能窗统一窗壳、五窗互斥单开、各窗自带关闭钮(营地开合组)
# 判据出处: 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/ux/camp.md:20 木桩命中区→天赋树界面入口(场景点选路由)
# 判据出处: 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/ux/camp.md:41 钥石余额常驻画面顶部 HUD 栏(余额文案组)
# 判据出处: 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/ux/camp.md:18 宝箱命中区→秘宝匣界面入口
# 判据出处: 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:
@@ -76,7 +76,7 @@ func _ready() -> void:
await get_tree().process_frame
# ---- 入口选路停点(关卡开始即出节点图——level_started 后、首战前弹选路层;
# candidate_rect 命中矩形真点 pick 入口单候选进首战)----
# oracle: design/ux/level-map.md:11 入口停点候选数=入口个数(入口单候选断言)
# 判据出处: design/ux/level-map.md:11 入口停点候选数=入口个数(入口单候选断言)
# ---- 独立性断言(独立屏激活期战斗屏宿主整体隐藏,零透出)----
if not main.ui.modals.run.level_screen.visible:
problems.append("关卡开始未弹入口选路停点(level_screen 应可见)")
@@ -92,7 +92,7 @@ func _ready() -> void:
if not main.ui.screens.battle_root.visible or main.ui.modals.run.level_screen.visible:
problems.append("选路提交战斗节点后未切回战斗屏(屏级互斥恢复失守)")
# ---- 放弃入口·冒险模式可见 + 战斗中按下被拒(真实按钮路径——入口挂选路停点,战斗中拒绝为防御分支)----
# oracle: design/ux/level-map.md:42 放弃冒险(任意选路停点)→ 冒险落幕——战斗域单事务在飞时拒绝
# 判据出处: 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/ux/battle.md:122 B 区构建组:主角与敌人站桩、明细带挖掘期收起、轮次指示、棋盘与 C 区/秘宝槽、首战进行中
# 判据出处: 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:
@@ -119,7 +119,7 @@ func _ready() -> void:
problems.append("B 区敌方站桩未构建(%d 个)" % main.ui.battle.stage.enemy_box.get_child_count())
if main.ui.battle.board_zone.ap_bar._dots.get_child_count() != int(main.db.run.action_economy.ap_per_round):
problems.append("D 区 AP 点阵数量错误(%d)" % main.ui.battle.board_zone.ap_bar._dots.get_child_count())
# oracle: design/gdd/systems/排雷循环/行动经济.md:258 开轮硬重置:行动点回到表值(AP 点阵数=ap_per_round)
# 判据出处: design/gdd/systems/排雷循环/行动经济.md:258 开轮硬重置:行动点回到表值(AP 点阵数=ap_per_round)
if main.ui.battle.board_zone.ap_bar.refresh_remaining_text() == "":
problems.append("D 区弃图刷新按钮文案缺失")
if main.ui.battle.board_zone.toast == null:
@@ -136,7 +136,7 @@ func _ready() -> void:
# E 区无道具面板——四区构建由其余断言覆盖
# ---- 敌方站桩投影(顺位徽标/意图三档动作动画占位——敌人系统×4,全站桩)----
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:275 敌人呈现:意图占位三档可见性——普通攻击零表示、类型级提示(站桩投影组)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:275 敌人呈现:意图占位三档可见性——普通攻击零表示、类型级提示(站桩投影组)
# B 区站桩子件伴随件化(壳层 tooltip 载体)——断言口径见台账 171:
# 伴随件化(壳层 tooltip 载体),穿透明(子件 _name_label/_armor_icon/_intent_badge)改本体
# 探针出口 stage.enemy_body_at(i).name_line_probe()/intent_probe()——呈现口径逐字保持
@@ -163,7 +163,7 @@ func _ready() -> void:
elif not bool(main.ui.battle.stage.hero_standee.portrait_probe().get("has_tex", false)):
problems.append("主角立绘纹理未落位(显示档 hero=144——显示档批)")
# ---- BUG-0001 回归(tooltip 入树后 set_effects 槽位不错位——结算软锁修复沉淀)----
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:274 状态槽图标+层数+剩余次数、悬停见来源(tooltip 回归组)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:274 状态槽图标+层数+剩余次数、悬停见来源(tooltip 回归组)
var slots_row = main.ui.battle.stage.hero_standee.slot_row # BUG-0001 回归随组件迁入站桩(tooltip/槽位错位同源)
var probe_eff: Array = [{"effect_id": "attack_up", "display_name": "增伤", "layers": 1,
"remaining_settlements": 1, "source_class": "safe_cell", "round_index": 1, "effective_value": 50}]
@@ -189,7 +189,7 @@ func _ready() -> void:
# ---- 图标键回归(HUD框架 图标键契约:状态表九行全映射〔定稿 PNG 纹理〕、
# 格面资源 cell_type 九型全映射〔含 gp 键〕;查不到的键 fail-fast + ? 占位
# ——此处钉死映射不残缺,未知键断言由 _icon_tex/_badge_glyph 承担)----
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:274 极性按键级表呈现——状态图标键映射不残缺(图标键回归组)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:274 极性按键级表呈现——状态图标键映射不残缺(图标键回归组)
for eid in ["attack_up", "ac_up", "alert", "weaken", "enrage", "off_guard", "sapped", "empower", "fortify"]:
if not StatusSlotRow.EFFECT_ICON.has(eid) or StatusSlotRow.EFFECT_ICON[eid] == null:
problems.append("状态图标九键纹理映射缺失(%s)" % eid)
@@ -214,7 +214,7 @@ func _ready() -> void:
# ---- L2 单场段(debug_session 单场原语 + 去饱和接口可用性;雾格机制已整体移除,不再断言变体呈现)----
# 单场无用户入口——探针直调 debug_session 原语(battle_created/ready 仍经组合根接线;
# _reset_run_scope 复现 _start_single 的提示去重复位)
# oracle: design/gdd/systems/排雷循环/地形变异.md:156 布置域:变异格全在初始展开区外(L2 布置域组)
# 判据出处: design/gdd/systems/排雷循环/地形变异.md:156 布置域:变异格全在初始展开区外(L2 布置域组)
main.begin_session(false)
main._debug.start_single(2)
await get_tree().process_frame
@@ -237,7 +237,7 @@ func _ready() -> void:
# ---- GP 终局行:summary gp_final 键取数+胜利面板 GP 行呈现(C5:GP 行只上冒险终局面板;
# 断言面 = summary 键,无影子账清零转存)----
# (置于战斗态断言之后——合成收束会灰化站桩并清 settlement 引用,具污染性)
# oracle: design/ux/settlement-summary.md:20 战利品行=GP 增量+总量(冒险终局面板 GP 行组)
# 判据出处: 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):
+11 -11
View File
@@ -89,7 +89,7 @@ func _ready() -> void:
# ---- 生涯页:入口=帐篷旁笔记本(卡④ T3 改挂——场景点选信号真实路由→组合根 _open_career)→
# 结构断言族→只读巡检(契约 3)+零动效契约+零跳转契约(提纲 P4 甲)→组合根真实路径关闭。
#(登记序/树序契约统一在全模态 _modal_registry_contract——boot 段跑)----
# oracle: design/ux/camp.md:19 帐篷旁笔记本→生涯记录;生涯页返回钮口径由结构校验兜底(生涯段)
# 判据出处: 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/ux/camp.md:45 ESC 优先序:有窗先关最上层窗、无窗才进浮窗;各窗自带关闭钮(Esc 段)
# 判据出处: 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/ux/level-map.md:3 图上点击选路·候选按钮退役(冒险簇选路真点组)
# 判据出处: 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()
@@ -243,7 +243,7 @@ func _ready() -> void:
# ---- 图例层(旧模态、首过结构族):ap_bar legend_toggled
# 信号真路径开(board_zone 信号→组合根 _on_legend_toggled→_open_modal)→结构断言族→
# closed 信号真路径关(close()→组合根 _on_modal_closed)。
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:88 图例面板=模态族:统一登记序栈、入栈出栈、关闭逐层恢复(图例组)
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:88 图例面板=模态族:统一登记序栈、入栈出栈、关闭逐层恢复(图例组)
main.ui.battle.board_zone.ap_bar._btn_legend.pressed.emit() # 前提注入(保留 emit):emit 选路路径下 battle_root 未及显示且输入锁在(实证在案),真点不可达——图例开启的真输入承载随「route 信号路径屏幕切换时序」发现一并呈报待裁
_settle()
if not main.ui.modals.run.legend_overlay.visible:
@@ -406,7 +406,7 @@ const _PAUSE_READONLY_SIGNALS: Array[String] = [
func _status_readonly_contract(main: Node) -> void:
# oracle: design/engineering/code-conventions.md:21 视图零核心写调用、依赖单向(status 只读巡检组)
# 判据出处: design/engineering/code-conventions.md:21 视图零核心写调用、依赖单向(status 只读巡检组)
var overlay: Control = main.ui.modals.run.status_panel
var stack: Array = [overlay]
var conn_count := 0
@@ -453,7 +453,7 @@ func _status_zero_motion_contract(main: Node) -> void:
# 清单外零连接。白名单={视图自身 _emit_closed〔返回钮〕, 组合根 _on_career_closed};页内任何
# 购买/导航/写入方向的新连接一律红,须先入册审查。
func _career_readonly_contract(main: Node) -> void:
# oracle: design/engineering/code-conventions.md:21 视图零核心写调用、依赖单向(career 只读巡检组)
# 判据出处: design/engineering/code-conventions.md:21 视图零核心写调用、依赖单向(career 只读巡检组)
var overlay: Control = main.ui.modals.menu.career_overlay
var stack: Array = [overlay]
var conn_count := 0
@@ -500,7 +500,7 @@ func _career_zero_motion_contract(main: Node) -> void:
# _career_registry_contract 的断言(career=状态面板后/确认弹窗前、树序同步)由本全量合同覆盖)
# ⑭选档界面契约(卡③ sl1/sl2/sl3/sl4/sl9——呈现面全部来自喂入数据,视图零自判)
func _save_select_contract(main: Node, tpl: String) -> void:
# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 三存档位独立文件、激活态由选档界面建立(选档契约组)
# 判据出处: design/gdd/systems/表现与支撑/存档序列化.md:61 三存档位独立文件、激活态由选档界面建立(选档契约组)
var sso: Node = main.ui.modals.menu.save_select_overlay
# sl1 三槽呈现:槽卡计数==3+零滚动容器(禁滚动长列表形态——契约 1)
var rows: VBoxContainer = sso._rows_box
@@ -580,7 +580,7 @@ func _collect_labels(node: Node, out: Array[String]) -> void:
func _modal_registry_contract(main: Node) -> void:
# oracle: design/engineering/code-conventions.md:54 模态覆盖层层级规则——新增覆盖层必须登记 z 序位置(登记序契约组)
# 判据出处: design/engineering/code-conventions.md:54 模态覆盖层层级规则——新增覆盖层必须登记 z 序位置(登记序契约组)
var list: Array = main.ui.flow.stack._list
# ① 登记册序==合同模态序(逐位点名——合同是登记序唯一事实源)
var contract_modals: Array = []
@@ -648,7 +648,7 @@ func _modal_registry_contract(main: Node) -> void:
# ⑬career 零跳转契约(提纲 P4 甲——页内零「前往他处」入口:除「返回」外任何导航钮=红)
func _career_nojump_contract(main: Node) -> void:
# oracle: design/ux/camp.md:19 生涯记录入口与返回钮口径由结构校验兜底——页内零导航入口(零跳转契约组)
# 判据出处: 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/ux/battle.md:154 主角站桩立绘(无框立绘+落影)——站桩内容矩形⊆舞台(站桩适配组)
# 判据出处: design/ux/battle.md:154 主角站桩立绘(无框立绘+落影)——站桩内容矩形⊆舞台(站桩适配组)
var st: Node = main.ui.battle.stage
if st == null or st.hero_standee == null:
problems.append("[standee] 舞台/主角站桩缺失(断言中断)")
@@ -743,7 +743,7 @@ func _in_scroll(c: Control) -> bool:
func _audit(screen: String, roots: Array) -> void:
# oracle: design/engineering/architecture/architecture.md:168 组合根交接面与呈现装配——屏结构审计宿主面(结构审计组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·呈现层」D1 行(组合根交接面) 组合根交接面与呈现装配——屏结构审计宿主面(结构审计组)
for r in roots:
if r != null and is_instance_valid(r):
_force_layout(r)
+6 -6
View File
@@ -43,7 +43,7 @@ func _ready() -> void:
# ① 全局浮层角标开窗(调试入口=UI 最顶层浮层,任何 UI 不遮挡——
# main 直接子层、LAYER_CONTRACT 末位 float 类;boot 菜单优先不开浮窗,角标真实点击路径开窗;
# 悬浮于菜单之上——开始层不隐藏;树序新基准=登记册末位(确认弹窗之后=绘制序最上))
# oracle: design/engineering/test-conventions.md:49 调试面=DebugOverlay/DS_DEBUG——发布态恒隐无 UI 开启口、开发期临时启用(①开窗层级组)
# 判据出处: design/engineering/test-conventions.md:49 调试面=DebugOverlay/DS_DEBUG——发布态恒隐无 UI 开启口、开发期临时启用(①开窗层级组)
main._show_debug_overlay() # 发布态角标恒隐(main.gd:138 _debug_entry 缺省 false)——角标信号路径已退役(:123 同款先例),开窗经组合根直调承载
await get_tree().process_frame
if not main.ui.modals.menu.debug_overlay.visible:
@@ -57,7 +57,7 @@ func _ready() -> void:
if str(boosted_atk) not in main.ui.modals.menu.debug_overlay.effect_text() or str(boosted_hp) not in main.ui.modals.menu.debug_overlay.effect_text():
problems.append("生效预览未联动(%s)" % main.ui.modals.menu.debug_overlay.effect_text())
# ③ 应用(真实信号路径:apply_requested → 组合根)——E4 后加成零表写,生效断言移至 ⑧ 战斗快照
# oracle: design/engineering/test-conventions.md:49 调试面加成链路:滑条联动/应用/重置、hero 表运行期只读(②③④⑦组)
# 判据出处: design/engineering/test-conventions.md:49 调试面加成链路:滑条联动/应用/重置、hero 表运行期只读(②③④⑦组)
var apply_btn: Button = ProbeUiUtil.find_button(main.ui.modals.menu.debug_overlay, "应用并关闭")
if apply_btn == null:
problems.append("调试浮窗「应用并关闭」钮缺失")
@@ -72,7 +72,7 @@ func _ready() -> void:
ProbeUiUtil.push_click(main, reset_btn)
# ⑤ 批量模拟全链路(N=10——走 level_autoplay 与 headless 扫描同口径;表基线由组合根复位;
# 菜单态上下文未锁定=按钮可点,is_running 语义=模拟在跑〔18卡⑦ 与对局禁用态拆分〕)
# oracle: design/engineering/test-conventions.md:49 调试面批量模拟链路:N=10 输出关卡完成率/剩余 HP/败北节点(⑤组)
# 判据出处: 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)
# 折叠区控件真输入通道(test-conventions §2——先滚轮滚入可视区再点):sim 钮在滚动列表 boot
@@ -155,7 +155,7 @@ func _ready() -> void:
# ⑨ 对局中可开(18卡⑦ P2 甲/T2 防护——S1+ 停机点回归闸):战斗屏角标真实信号路径开窗;
# 棋盘输入不锁、状态角标不置灰(「默认开×遮挡复活」下必锁死的破约回归面);
# 对局中批量模拟/两层调试两钮禁用;当局数据六项与只读访问器逐项一致;关闭钮真实路径收窗恢复
# oracle: design/engineering/test-conventions.md:49 调试面对局态:不锁棋盘输入/两钮禁用/当局数据只读一致(⑨组)
# 判据出处: design/engineering/test-conventions.md:49 调试面对局态:不锁棋盘输入/两钮禁用/当局数据只读一致(⑨组)
var dbg_badge: Button = main.ui.screens.debug_badge
if dbg_badge == null or dbg_badge.visible:
problems.append("对局中调试浮层角标非恒隐(发布态语义失守——卡⑦ 删写死行批)")
@@ -199,7 +199,7 @@ func _ready() -> void:
_overlay_zero_motion_contract(main, problems)
# ⑩ 种子三分单点断言(18卡⑦ C1——判定只落 _seed_value() 单点;对局态不影响本判定面):
# 显式合法整数全场景优先(含首局)→ 首局(存档布尔 false)恒 42 → 非首局空框引擎熵连抽不全等 42
# oracle: design/engineering/architecture/architecture.md:168 seed_value=组合根公开服务面(⑩种子三分判定单点组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·呈现层」D1 行(组合根交接面) seed_value=组合根公开服务面(⑩种子三分判定单点组)
main.ui.modals.menu.debug_overlay.seed_edit.text = "777"
if main.seed_value() != 777:
problems.append("显式种子输入未优先(_seed_value != 777——C1 ①失守)")
@@ -240,7 +240,7 @@ func _ready() -> void:
# 全连接巡检——白名单={视图自身八方法 + 注册面模态编排口 ui.flow.close(closed 外发消费——
# 注册面批把该连接目标从组合根转发壳改为 flow 真方法,机检白名单随批登记同一连接)};
# 当局数据区块=纯标签零连接在此被一并约束(新增任何连接=红须入册审查)。
# oracle: design/engineering/code-conventions.md:21 视图零核心写调用、依赖单向(白名单外零连接巡检组)
# 判据出处: design/engineering/code-conventions.md:21 视图零核心写调用、依赖单向(白名单外零连接巡检组)
func _overlay_readonly_contract(main: Node, problems: Array) -> void:
var overlay: Control = main.ui.modals.menu.debug_overlay
var stack: Array = [overlay]
+3 -3
View File
@@ -78,7 +78,7 @@ static func _deduce_single(minesweeper) -> Dictionary:
# 〔轮末 weaken + 魔法 首次充能〕且雷上封印不阻连锁)· 卡死且刷新资源可用 → 轮内刷新
# (经 settlement.player_request_refresh 核心编排)· 否则停手(残 AP 1:1 转存 RAP——下轮刷新资源)。
# 推理规则集 = 棋盘生成 F1 measure_inferability 同族(single + subset——solver_rules=single_subset 冻结域);
# 口径差异 = unknown 集不含 棋盘生成 生成期 safe_set oracle(真实玩家约束的未知邻格含危险格)——比生成期闸
# 口径差异 = unknown 集不含 棋盘生成 生成期 safe_set 真值(真实玩家约束的未知邻格含危险格)——比生成期闸
# 保守,卡死率略高属口径诚实。输入只经 排雷玩法 查询面(reveal_record/is_revealed/has_seal/current_board
# 的邻居与可达面),禁读棋盘雷区真值(测推理上界,非全知作弊)。
# 待拍板(未实现):非卡死主动刷新、囤刀倾泻 vs 即用即耗、完美板目标加权。
@@ -148,8 +148,8 @@ static func _deduce(minesweeper) -> Dictionary:
changed = true
# 0 格连锁算子(架构整改批补齐——与棋盘生成 _apply_chain_rule 对位):safe 集中未揭示格
# 挖开必触发 0 格连锁(连锁只经安全格扩散=闭包集全安全,真实玩家挖 c 免费获得的信息面),
# 经 preview_reveal 真实闭包取新开格集并入 safe——双口径显式登记:unknown 不含 safe_set oracle
# 〔口径差异=board_generator 版含 oracle,本版 no-peek——:80-83 注释在账〕
# 经 preview_reveal 真实闭包取新开格集并入 safe——双口径显式登记:unknown 不含 safe_set 真值
# 〔口径差异=board_generator 版含真值,本版 no-peek——:80-83 注释在账〕
for c in (safe.keys() as Array).duplicate():
if board.is_safe(c) and not minesweeper.is_revealed(c):
for rc in minesweeper.preview_reveal(c):
+32 -13
View File
@@ -114,10 +114,12 @@ tests/
| 同参数同种子逐位复现、异种子不退化 | 棋盘生成 | `BoardGenerator.generate()` | `board_generator_test.gd:_test_same_seed_bit_identical`;`board_gen_reward_test.gd:_smoke_board_generator` |
| 可推理率与清图可达(合法性先于质量分)、重试有界、降级诚实 | 棋盘生成 | `BoardGenerator.measure_inferability()`/`BoardGenerator.is_clearable()` | `board_generator_test.gd:_test_clearable_and_inferability_selfconsistent`;`board_generator_test.gd:_test_target_mine_count_single_source`;`board_gen_reward_test.gd:_smoke_board_generator` |
| 装板与开局免费展开(封锁格无资源、计入清图) | 排雷玩法 | `MinesweeperRules.install_board()` | `minesweeper_rules_test.gd:_test_install_invariants`;`seal_polarity_test.gd:_smoke_resource_surface` |
| 挖/封印合法性与可辨拒绝(拒绝零副作用) | 排雷玩法 | `MinesweeperRules.settle_batch()` | `minesweeper_rules_test.gd:_test_dig_face_rules`;`minesweeper_rules_test.gd:_test_seal_place`;`minesweeper_rules_test.gd:_test_reject_out_of_catalog` |
| 挖/封印合法性与可辨拒绝(拒绝零副作用、挖封印格被拒) | 排雷玩法 | `MinesweeperRules.settle_batch()` | `minesweeper_rules_test.gd:_test_dig_face_rules`;`minesweeper_rules_test.gd:_test_seal_place`;`minesweeper_rules_test.gd:_test_reject_out_of_catalog`;`minesweeper_rules_test.gd:_test_dig_sealed_rejected`;`minesweeper_rules_test.gd:_test_classify_seal_clears`(计划——需求卡片「封印动作改版批」2026-11-07 锚定) |
| 棋盘揭示预览只读口(零状态写入/与实挖揭示集同口径) | 排雷玩法 | `MinesweeperRules.preview_reveal()` | `minesweeper_rules_test.gd:_test_preview_reveal_readonly` |
| 封印不可逆与挖开封印格放行(兑付改判四情况) | 排雷玩法 | `MinesweeperRules.settle_batch()` | `seal_polarity_test.gd:_smoke_seal_irreversible_quadrants`;`minesweeper_rules_test.gd:_test_seal_place` |
| 棋盘封印印记呈现 | 排雷玩法 | `BoardCellUnit.edge` → `SealMarkLayer.setup()` | `seal_mark_params_test.gd:_test_param_values`;`seal_mark_params_test.gd:_test_fog_gate` |
| 封印受理即揭示(身份可查、受理零事件) | 排雷玩法 | `MinesweeperRules.settle_batch()` | `minesweeper_rules_test.gd:_test_seal_reveal_identity`(计划——需求卡片「封印动作改版批」2026-11-07 锚定) |
| 清图口径换代(挖开∪封印、封印可完成清图) | 排雷玩法 | `MinesweeperRules.settle_batch()` | `minesweeper_rules_test.gd:_test_clear_by_seal`(计划——需求卡片「封印动作改版批」2026-11-07 锚定) |
| 棋盘封印印记呈现(已揭示格承载+已封印格零动作) | 排雷玩法 | `BoardCellUnit.edge` → `SealMarkLayer.setup()`;`ui_input_probe` 真输入动线 | `seal_mark_params_test.gd:_test_param_values`;`seal_mark_params_test.gd:_test_fog_gate`;`seal_mark_params_test.gd:_test_seal_on_revealed_visible`(计划——需求卡片「封印动作改版批」2026-11-07 锚定) |
| 棋盘格面绘制显示(渲染闸棋盘三图 golden 逐像素——变异标记/奖励徽章/移除退色) | 排雷玩法 | `board_shot_probe.tscn` 窗口化真渲染(`BoardView.rebuild()` 固定种子单关直构)→`golden_compare.py` 逐像素 | `board_shot_probe.gd:_ready`(棋盘三图采集——渲染闸基线面)(计划——需求卡片「QA覆盖余量收口批」2026-11-07 锚定) |
| AP 扣费、末点耗尽即收轮与战斗收束清零 | 行动经济 | `MinesweeperRules.settle_batch()`(→`ActionEconomy.try_spend()`/`ActionEconomy.notify_combat_end()`) | `seal_polarity_test.gd:_smoke_seal_ap_exhausted`;`round_flow_skill_pool_test.gd:_smoke_full_battle`;`round_flow_skill_pool_test.gd:_smoke_active_refresh` |
| 停手转存储备与轮内刷新(上限/AP 不重置/刷新图零清图奖励) | 行动经济 | `BattleOrchestrator.player_request_refresh()`/`MinesweeperRules.settle_batch()` | `round_flow_skill_pool_test.gd:_smoke_active_refresh`;`seal_polarity_test.gd:_smoke_refresh_provenance_guard`;`refresh_feats_test.gd:test_no_feats_baseline` |
| 换图系刷新链路与效果词表接线 | 行动经济×战斗结算×奇物 | `MinesweeperRules.settle_batch()`/`BattleOrchestrator.player_request_refresh()` | `effect_words_t1_test.gd:_init`;`forge_table_test.gd:_test_effect_kinds_closed`;`refresh_feats_test.gd:test_board_refresh_fact_only_on_success` |
@@ -132,7 +134,8 @@ tests/
| 能力 | 系统 | 真实入口 | 代表用例(文件:函数) |
|---|---|---|---|
| 拒绝反馈分面可辨与未知兜底文案 | HUD框架 | `HudPresenter.outcome_text()` | `hud_presenter_test.gd:_test_reject_text_by_reason`;`hud_presenter_test.gd:_test_unknown_reason_fallback`;`hud_presenter_test.gd:_test_accepted_text` |
| 结算明细带逐行呈现与排播节奏 | HUD框架 | `BattleLogView.append_row()`/`RowPacer.pace_delay_for()` | `theme_projection_test.gd:_smoke_battle_log_view`;`player_copy_sweep_test.gd:_sweep_source_names`;`pacing_queue_test.gd:_init` |
| 结算期零逐行文字与 FX 排播链保持 | HUD框架 | `RowPacer._consume_row()`/`BattleStageView.on_step_row_fx()`(真实停手→轮末结算→B 区场景树与 FX 调用面) | `battle_log_zero_text_test.gd:_smoke_settle_zero_rows`;`pacing_queue_test.gd:_assert_fx_chain`;`standee_fx_sweep_test.gd:_init` |
| 日志 step 行零文案与结构化判读等价 | 表现与支撑 | `PlaytestLogger._on_step_row()`→`PlaytestLogger._append()`(真实结算→落盘 JSONL 读回) | `playtest_logger_step_fields_test.gd:_assert_no_text_and_full_payload`;`reward_ineffective_reason_test.gd:_assert_four_reasons` |
| 模态覆盖层编排与输入锁 | HUD框架 | `ModalFlow.locks_input()`/`ModalFlow.open_after_playback()` | `modal_flow_test.gd:_init`;`reward_overlay_test.gd:_run` |
| 模态句柄分域寻址面 | 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` |
@@ -142,10 +145,10 @@ tests/
| 地形瓦片套与格面数字可读 | 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`(旧行保留,删行归写卡段后续,实现批禁写) |
| 棋盘合并岩体与连续地面(深度遮挡、投影、崖面) | 美术圣经 | `BoardTerrainLayer.setup()`/`BoardTerrainLayer.terrain_metrics()` | `board_terrain_test.gd:_test_merged_mass_geometry`;`board_terrain_test.gd:_test_projection_ratios`;`board_terrain_test.gd:_test_depth_order` |
| 棋盘浮空数字与引导线(最顶层、顶部预留) | 美术圣经 | `BoardFloatLayer.set_entries()`/`BoardFloatLayer.label_metrics()`/`BoardLabelShadowLayer.setup()` | `board_float_label_test.gd:_test_label_geometry`;`board_float_label_test.gd:_test_topmost_layer`;`board_float_label_test.gd:_test_contrast` |
| 棋盘顶面锚点与迷雾几何(封印、微光、变异的岩顶偏移) | 美术圣经 | `BoardView.refresh()`/`BoardView._visual_rect_for()`/`CellUnit.fog_state` | `board_terrain_test.gd:_test_top_face_anchor`;`board_terrain_test.gd:_test_fog_rect` |
| 棋盘顶面锚点与迷雾几何(微光、变异岩顶偏移,封印格心) | 美术圣经 | `BoardView.refresh()`/`BoardView._visual_rect_for()`/`CellUnit.fog_state` | `board_terrain_test.gd:_test_top_face_anchor`;`board_terrain_test.gd:_test_fog_rect` |
| D 区行动资源侧栏(AP 左栏、RAP 右栏、全高棋盘) | HUD框架 | `ApBarView.set_ap()`/`ApBarView.set_rap()`/`BoardZoneView._on_board_area_resized()` | `board_side_rail_test.gd:_test_side_rail_layout`;`board_side_rail_test.gd:_test_signals`;`stage_background_test.gd:_test_side_rail_width_from_table` |
| 棋盘点击命中与格边公式(逻辑格矩形) | HUD框架 | `BoardView.set_cell_area()`/`BoardView.cell_center_local()` | `board_terrain_test.gd:_test_cell_formula_base`;`board_terrain_test.gd:_test_hit_test_logical_rect`;`layout_probe.gd:_ready` |
| 旧瓦片退役与棋盘零底牌 | 美术圣经 | `BoardView.rebuild()`/`BoardView.refresh()`(资产装载与绘制路径) | `board_terrain_test.gd:_test_legacy_assets_absent`;`board_float_label_test.gd:_test_no_plate_path` |
| 旧瓦片退役与普通数字零底牌 | 美术圣经 | `BoardView.rebuild()`/`BoardView.refresh()`(资产装载与绘制路径) | `board_terrain_test.gd:_test_legacy_assets_absent`;`board_float_label_test.gd:_test_no_ordinary_plate_path` |
| 站桩敌人呈现与结算演出落点 | 战斗表现 | `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` |
@@ -178,7 +181,7 @@ tests/
| 终局结算框关闭动线零引擎报错 | 战斗表现 | 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 锚定) |
| 控件动线扩面真输入可达(图例关闭/节点继续/三选一与商店/秘宝架/树高档与先手/弃图链/地图放弃/进入下一层/Esc 设置/减动效开关)+cell_mark 模态断言 | HUD框架×各屏 | 合成 InputEvent 经 `Viewport.push_input()` 真点(`ProbeUiUtil.push_click_at()`;扩面十二段,高档钮经调试注钥使能) | `ui_click_probe.gd:_ready`(动线扩面段);`ui_struct_probe.gd:_ready`(cell_mark 段)(计划——需求卡片「QA覆盖余量收口批」2026-11-07 锚定) |
| 控件动线扩面真输入可达(图例关闭/节点继续/三选一与商店/秘宝架/树高档与先手/弃图链/地图放弃/Esc 设置/减动效开关) | HUD框架×各屏 | 合成 InputEvent 经 `Viewport.push_input()` 真点(`ProbeUiUtil.push_click_at()`;扩面十一段,高档钮经调试注钥使能) | `ui_click_probe.gd:_ready`(动线扩面段)(计划——需求卡片「QA覆盖余量收口批」2026-11-07 锚定) |
**旅程段七:数据配置与跨系统契约**
@@ -218,10 +221,10 @@ tests/
| 战斗循环 | 战斗表现.md | 站桩敌人呈现与结算演出落点;站桩呈现定值与素材绑定;站桩显示档与素材分辨率绑定 | 已覆盖 | 死亡灰化与减动效演出档以探针+人工目检为终态(2026-11-02 用户裁除名,不再补自动断言) |
| 战斗循环 | 敌人系统.md | 技能池数据面与回放独立;Boss 半血解锁技门控;敌人技能池跨表链接校验;精英行:池继承合并、加载期校验与暂不接线 | 部分覆盖 | 精英行「开战快照拿到合并后池」随接线批验收;见待办清单「战斗节点遭遇编排方式」条 |
| 排雷循环 | 地形变异.md | 变异布置门控;表结构守卫:域校验/行序键/跨字段互锁/配额锚 | 已覆盖 | — |
| 排雷循环 | 排雷玩法.md | 装板与开局免费展开(封锁格无资源、计入清图);挖/封印合法性与可辨拒绝(拒绝零副作用);封印不可逆与挖开封印格放行(兑付改判四情况) | 已覆盖 | — |
| 排雷循环 | 排雷玩法.md | 装板与开局免费展开(封锁格无资源、计入清图);挖/封印合法性与可辨拒绝(拒绝零副作用、挖封印格被拒);封印受理即揭示(身份可查、受理零事件);清图口径换代(挖开∪封印、封印可完成清图) | 已覆盖 | — |
| 排雷循环 | 棋盘生成.md | 棋盘按参数行出图、数字恒真;同参数同种子逐位复现、异种子不退化;可推理率与清图可达(合法性先于质量分)、重试有界、降级诚实 | 已覆盖 | — |
| 排雷循环 | 行动经济.md | AP 扣费、末点耗尽即收轮与战斗收束清零;停手转存储备与轮内刷新(上限/AP 不重置/刷新图零清图奖励);下轮行动点修正排队与兑现 | 已覆盖 | — |
| 表现与支撑 | HUD框架.md | 拒绝反馈分面可辨与未知兜底文案;结算明细带逐行呈现与排播节奏;模态覆盖层编排与输入锁;未开格类型微光(8 邻作用集、五组色相、零玩法写入);UI 全局主题与按钮五态(含对比度锚);中文字体随包与字形覆盖;棋盘合并岩体与连续地面(深度遮挡、投影、崖面);棋盘浮空数字与引导线(最顶层、顶部预留);D 区行动资源侧栏(AP 左栏、RAP 右栏、全高棋盘);棋盘点击命中与格边公式(逻辑格矩形);旧瓦片退役与棋盘零底牌;棋盘顶面锚点与迷雾几何(封印、微光、变异的岩顶偏移);战斗叙述与终局文案(数值带入/胜负互异);玩家面文案与危险格格面字形;战斗屏三区几何与棋盘格边长(分辨率两档×四档板型);B 区舞台背景三关变体与连续地面层(按 board_type 选图/地面层高度读布局表/四柱光柱退役);模态句柄分域寻址面 | 已覆盖 | — |
| 表现与支撑 | HUD框架.md | 拒绝反馈分面可辨与未知兜底文案;结算期零逐行文字与 FX 排播链保持;模态覆盖层编排与输入锁;未开格类型微光(8 邻作用集、五组色相、零玩法写入);UI 全局主题与按钮五态(含对比度锚);中文字体随包与字形覆盖;地形瓦片套与格面数字可读(旧行保留,删行归写卡段后续,实现批禁写);棋盘合并岩体与连续地面(深度遮挡、投影、崖面);棋盘浮空数字与引导线(最顶层、顶部预留);D 区行动资源侧栏(AP 左栏、RAP 右栏、全高棋盘);棋盘点击命中与格边公式(逻辑格矩形);旧瓦片退役与普通数字零底牌;棋盘顶面锚点与迷雾几何(微光、变异岩顶偏移,封印格心);战斗叙述与终局文案(数值带入/胜负互异);玩家面文案与危险格格面字形;战斗屏三区几何与棋盘格边长(分辨率两档×四档板型);B 区舞台背景三关变体与连续地面层(按 board_type 选图/地面层高度读布局表/四柱光柱退役);模态句柄分域寻址面 | 已覆盖 | — |
| 表现与支撑 | 存档序列化.md | 存档写盘⇄读回往返保真;三槽位隔离与激活失败回滚;槽位摘要窥视与坏槽三态;启动态无激活不写盘;旧档迁移链与断点续玩零跳号;存档⇄RNG 快照往返保真 | 已覆盖 | — |
| 表现与支撑 | 教学引导.md | — | 未覆盖 | 教学形态与首遇时机待设计;见待办清单「教学形态未定」条 |
| 表现与支撑 | 数据配置.md | 全表装载、就绪态与表清单目录对账;进度坐标查行:精确命中/向上取整/封顶/警告去重;运行期越域致命与进程非 0 退出;加载期违规表拒绝:跨表悬空/计数器前缀/真视窄域;表结构守卫:域校验/行序键/跨字段互锁/配额锚 | 已覆盖 | — |
@@ -337,10 +340,26 @@ godot --headless --path . res://probe/<probe>.tscn
## Static Scan(提交前本地跑)
`tests/static_scan.sh` 实现静态扫描域 S1–S12 的可执行子集;S1–S10 的规则全集与动机见
`design/engineering/architecture/architecture.md`「测试与质量域」节,**S11/S12 两段的规则说明自持于本文件与
`tests/static_scan.sh` 头注**,这两段不另进架构文档。
新违规案例必须**先补扫描规则再修码**。
`tests/static_scan.sh` 实现静态扫描域 S1–S13;**本节是全部 S 系规则的规则全集与动机**(2026-11-07 架构收窄批迁驻——原指架构文档「测试与质量域」节,该文档已收窄为约束面)。适用域分档(产品码三目录 vs 非产品码三目录)与无机器闸的理由见 `design/engineering/code-conventions.md` §4 第 13 条。
新违规案例必须**先补扫描规则再修码**(code-conventions 缺陷-规范对照表纪律)。
S1–S10 扫描面止于产品码三目录(`game/core`+`game/data`+`game/presentation`);S11/S12 扫描面覆盖 game/ 全域;S13 扫描面=产品码三目录。
| # | 规则 | 判级与要点 |
|---|---|---|
| S1 | 玩法代码禁 `randomize()`、自建 `RandomNumberGenerator`(D2 Rule 5) | 硬失败;扫描面 game/core + game/presentation。非产品码三目录(`game/tests`/`game/probe`/`game/sim`)不进本闸与 S2——它们自建且**显式定种**的随机源是合法测量口径(同种子对拍改前改后读数),禁的是不定种形态 |
| S2 | 禁全局随机函数 `randi()` / `randf()` / `randf_range()`——全局 RNG 绕开 RngService | 硬失败,与 S1 同段;实例方法 `.randi()` 排除;`rng-seed-exempt` 豁免注记在案 |
| S3 | core/ 禁 ConfigDB 外 data 表加载(`load(` / `preload(` / `ResourceLoader.load`;schema class_name 直引豁免)(D2 Rule 2) | **warning 级**(不参与 fail 判定)——class_name 直引误报需人工复核;路径=`game/core`;豁免 `config_db.gd` 自身——它就是数值单一事实源的定义所属文件。本行即状态事实源(2026-11-03 架构审查 P1 裁维持 warning 级不升硬,升硬前提=可靠的直引误报排除判据,该判据未立),禁在别处另列 |
| S4 | core/ 禁 `extends Control/Node2D`、禁 `.tscn` preload(D2 Rule 7) | 硬失败 |
| S5 | core/ 禁裸引用 autoload 全局标识符(`ConfigDB` / `RngService` 直接写=--script 模式 parse error)——构造注入 only | 硬失败;形态=标识符后紧跟 `.` 的成员访问,排除注释行与字符串字面量 |
| S6 | core↔core 信号连接只许方法 Callable(禁 lambda 捕获 self);发射者禁持有订阅者反向引用 | 硬失败(2026-10-25 落闸:内联 lambda 禁令机器闸——同行与跨行两形态、豁免注记 `s6-signal-exempt`);「发射者禁反向引用」句静态判据不成熟,归人工评审承接、不造弱判据 |
| S7 | B1 编排唯一性:B2/B3/B4 API 符号命中文件 ⊆ B1 文件集;A2/B3/B4 零相互直调(B1 AC-43) | 硬失败;**符号清单=从 B2/B3/B4 三族文件自动提取公开方法面**(非下划线 func 名,无手写清单);B1 域文件集清单单点=本脚本 `B1_SET`(脚本即清单);豁免注记 `s7-b1-exempt`;缺位自检在位 |
| S8 | 结算路径 `await` 零命中(B1 结算/编排文件集) | 硬失败,排除注释与字符串;文件集单点=`B1_SET`;自检两件:①清单内文件缺位即红;②**目录域反向对账**——`game/core/settlement/` 实有 `.gd` 未登记进 `B1_SET` 即红;装板/授予/效果端口/掷格四件结算码住 `game/core` 根下,纯目录域覆盖不住,根下新增由写卡段申报承接 |
| S9 | 类型闸(`tools/audit_type_hints.py`):产品码无类型容器/有值返回缺类型/参数缺类型(R1/R2/R3 细则=code-conventions §4 第 7/8/12 条) | 硬失败;扫描面缺目录或零个 `.gd` 判红(防空转自检),归绿时打印扫描面件数供核 |
| S10 | 文件头闸(`tools/audit_file_headers.py`):头注 `##` 载体+日期/变更叙事黑名单(细则=code-conventions §1.4.1) | 硬失败;同款防空转自检 |
| S11 | 代码注释禁用词闸 | 规则说明见下段 |
| S12 | 效果引擎无状态化旧件零残留闸 | 规则说明见下段 |
| S13 | 第三方质量规则闸(`tools/gdstyle_gate.py`,钉 gdstyle v0.4.0:真缺陷类规则——重复 load、循环内 await、循环内分配、不可达代码等) | 硬失败;基线键=文件\|规则\|函数名(类/文件级=文件\|规则),数字漂移不假红,同键指标恶化超基线登记值即红;存量以 `tools/gdstyle_baseline.txt` 欠账簿登记;扫描面=产品码三目录 |
S11 代码注释禁用词:扫描 game/ 全域 .gd/.tres/.md/.json/.sh 的注释面;禁词全部由 `design/gdd/术语表.md`「退役词对照」第一列解析供给(脚本内不内联任何词面——单一事实源);解析结果再按「巢穴」「旗」「flag_*」三条整行排除,另设同形异义豁免「去重闸」(封印重判去重的机制判别器,与某闸档退役词后二字同形——行内含「去重闸」整行跳过);三层豁免(闸门自指/显式留档 20 处〔契约原名引述与带日期历史读数注释,清单见 `tests/static_scan.sh` 头注〕/非注释面天然不取);两道自检(扫描面文件数非空+词表解析非空)。
+1 -1
View File
@@ -19,7 +19,7 @@
## 时效判定纪律
基线冻结于 2026-09-30;关卡视口两 Shot(lmap_entry/lmap_boss)冻结于 2026-11-05——两 Shot 面口径(只对拍入口停点与 Boss 节点)见上文现持清单行。**判定先于动作**:每代换代面(视觉/素材/布局改动)
基线冻结于 2026-09-30;关卡视口两 Shot(lmap_entry/lmap_boss)冻结于 2026-11-05;棋盘三图 2026-11-07 换代(采集态扩型——cell_mark_overlay 六绘制域入帧,需求卡片「QA覆盖余量收口批」卡修轮 2 V14 渲染通道裁定,新帧用户过目后重制)——两 Shot 面口径(只对拍入口停点与 Boss 节点)见上文现持清单行。**判定先于动作**:每代换代面(视觉/素材/布局改动)
跑批时先跑一次渲染闸看差异落不落在裁剪区与容差内——
- **判有效**(changed 远低于 2% 阈值):基线**留在位**,不得顺手重制;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 114 KiB

+1
View File
@@ -169,6 +169,7 @@ func _suites() -> Array:
# 样板件占位断言跑绿没有信息量,且占据「27 套件」计数。文件保留作模板(同类先例见下 fog_lantern 摘除行)。
{"path": "res://tests/unit/data_config/config_db_test.gd", "expect": 0},
{"path": "res://tests/unit/data_config/forge_table_test.gd", "expect": 0}, # 六路线 schema 正反(V1)+特效留空(V11)+几何价格梯(V2 半边)
{"path": "res://tests/unit/schema_vocab_violation_test.gd", "expect": 0}, # schema 词表违约词拒绝路径集中面(十七词族七检查——迭代 55 卡 T1〔QA余 V1〕;validate 诊断面直调非负面套件)
{"path": "res://tests/unit/data_config/level_gen_params_test.gd", "expect": 0}, # 参数档 schema 正反+访问器读回(节点图生成器核心批 卡① V2〔fv2〕)
{"path": "res://tests/unit/data_config/config_db_fatal_oob_test.gd", "expect": 1},
{"path": "res://tests/unit/data_config/treasure_target_violation_test.gd", "expect": 1}, # 真视窄域护栏
+3 -3
View File
@@ -16,7 +16,7 @@ var _checks: int = 0
func check(cond: bool, label: String) -> void:
# oracle: design/engineering/architecture/architecture.md:10 (测试层级四级——冒烟存活检查组)
# 判据出处: design/engineering/test-conventions.md「测试层三元组」冒烟测试行 (测试层三元组——冒烟存活检查组)
_checks += 1
if cond:
print(" [PASS] " + label)
@@ -35,7 +35,7 @@ func _ready() -> void:
# 开机前置面:autoload 注册与资源可加载(挂主屏之前先查,失败时主屏断言整段跳过)
func _check_autoloads() -> void:
# oracle: design/engineering/architecture/architecture.md:10 (测试层级四级——冒烟存活检查组)
# 判据出处: design/engineering/test-conventions.md「测试层三元组」冒烟测试行 (测试层三元组——冒烟存活检查组)
for autoload_name in ["ConfigDB", "RngService", "SaveManager", "MetaProgression"]:
var node: Node = get_node_or_null(NodePath("/root/" + autoload_name))
check(node != null, "autoload %s 在位" % autoload_name)
@@ -64,7 +64,7 @@ func _boot_main_scene() -> Node:
# 主屏存活面:组合根装配句柄在位(main.X 一级句柄契约,探针挂载假设的启动侧证明)
func _check_main_scene(main: Node) -> void:
# oracle: design/engineering/architecture/architecture.md:10 (测试层级四级——冒烟存活检查组)
# 判据出处: design/engineering/test-conventions.md「测试层三元组」冒烟测试行 (测试层三元组——冒烟存活检查组)
check(main != null and main.is_inside_tree(), "主屏场景已实例化并在树")
if main == null:
return
+34 -21
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# tests/static_scan.sh — 静态扫描域 S1–S10 全量在位
# 规则全集与动机:design/engineering/architecture/architecture.md「测试与质量域」节
# 规则全集与动机:game/tests/README.md「Static Scan」节(2026-11-07 架构收窄批迁驻)
# 正式工程(core/ 存在)时执行;否则优雅跳过(工程在 game/core——仓库根布局)。
# 纪律:新违规案例必须先补本脚本规则再修码(design/engineering/code-conventions.md 缺陷-规范对照表)。
set -u
@@ -313,17 +313,16 @@ else
fi
# ── S8 — 结算路径 await 零命中(B1 结算/编排文件集)──
# 规则(architecture.md「静态扫描域 S1–S8 承接清单」S8 行 / D2 AC-14 口径):结算本体是
# 可回放纯逻辑步骤流(同种子同输入逐位复现),await 会打断确定性——「模块归属」B1 行已写明
# 「禁 await——切片 88 冒烟实证」。本闸为防回归防线(2026-10-24 人工 grep 盘面干净)。
# 扫描面:B1 域文件集(与 architecture.md「模块归属」B1 行同口径)=battle_orchestrator.gd
# 规则(game/tests/README.md「Static Scan」节 S8 行 / D2 AC-14 口径):结算本体是
# 可回放纯逻辑步骤流(同种子同输入逐位复现),await 会打断确定性。本闸为防回归防线。
# 扫描面:B1 域文件集=battle_orchestrator.gd
# + battle_fx_host.gd + reward_grant.gd + board_installer.gd 三拆件 + settlement/ 五段段类 + damage_controller.gd,清单单点=B1_SET。
# 执行侧字面值豁免:脚本必须带字面值才能工作;文件集与架构文档冲突时以 B1 域文件集为准。
# 判据:文件集内出现 await 关键字即红(词边界 \bawait\b,任何形态——await 信号/协程/
# get_tree());排除注释行(行首 #/##)与字符串字面量(双引号包裹段内的 await 非代码),
# 口径仿 S1/S2 的 grep 排除手法。
# 自检:①清单内文件缺位(路径写错/被改名)即判红——防扫面为空静默绿;②settlement/ 目录域反向对账——新段类未登记即判红(见下)。
# B1 域文件集=单点清单(S8 await 禁扫面与 S7 豁免名单共用;改成员先改 architecture.md「模块归属」B1 行再改此处,2026-11-03 架构审查 P1 裁并点)
# B1 域文件集=单点清单(S8 await 禁扫面与 S7 豁免名单共用;2026-11-03 并点。清单单点=本脚本,
# 脚本即清单——2026-11-07 架构收窄后架构文档不再承载文件集登记,改成员只改此处)
B1_SET="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/settlement/round_cycle.gd game/core/damage_controller.gd game/core/settlement/battle_refresh_flow.gd game/core/effect_port.gd game/core/reward_roller.gd"
S8_FILES="$B1_SET"
S8_MISS=0
@@ -335,13 +334,13 @@ for f in $S8_FILES; do
done
# 自检二(2026-11-03 架构审查 P1 裁):目录域反向对账——settlement/ 实有 .gd 未登记进 B1_SET=该扫的没扫,判红。
# 只反向对 settlement/ 一个目录域:装板/授予/效果端口/掷格四件结算码住 game/core 根下,纯目录域覆盖不住,
# 根下新增仍由「模块归属」B1 行+写卡段申报面承接(禁把目录判据当成全域判据)。
# 根下新增仍由写卡段申报面承接(禁把目录判据当成全域判据)。
S8_UNREG=""
for f in $(find game/core/settlement -name '*.gd' 2>/dev/null | sort); do
case " $B1_SET " in *" $f "*) ;; *) S8_UNREG="$S8_UNREG $f" ;; esac
done
if [ -n "$S8_UNREG" ]; then
echo "[static_scan] S8 FAIL: settlement/ 有结算段类未登记进 B1 域文件集——await 禁扫面对它静默失效:$S8_UNREG(先改 architecture.md「模块归属」B1 行,再补 B1_SET)"
echo "[static_scan] S8 FAIL: settlement/ 有结算段类未登记进 B1 域文件集——await 禁扫面对它静默失效:$S8_UNREG(补 B1_SET 登记)"
S8_MISS=1
fi
if [ "$S8_MISS" -eq 1 ]; then
@@ -361,13 +360,13 @@ else
fi
# ── S7 — B1 编排唯一性:B2/B3/B4 API 符号命中文件 ⊆ B1 域文件集 ──
# 规则(architecture.md「静态扫描域 S1–S8 承接清单」S7 行 / B1 AC-43;依赖约束节同款):
# 规则(game/tests/README.md「Static Scan」节 S7 行 / B1 AC-43;依赖约束节同款):
# B2/B3/B4 的对外 API 只允许被 B1 域文件集调用——全调用面集中 B1、禁第二调用方。
# 附带覆盖「A2/B3/B4 零相互直调」:B2/B3/B4 自身文件 ∉ B1 文件集,命中彼家符号即红
# (同族自调用豁免只放自家符号,彼家符号照红)。
# 符号清单权威源:architecture.md「模块归属」各表「对外暴露」列——B2(unit_container)14 符号、
# B3(status_container)5 符号、B4(reward_economy)9 符号;脚本字面值与文档冲突时以文档为准
# (执行侧字面值豁免,同 S8 手法)。B4 表内 is_mapped 静默布尔投影注明批量工具专用、不在暴露面清单。
# 符号清单=从 B2/B3/B4 三族文件自动提取公开方法面(非下划线 func 名,含 static func)——
# 清单即代码现状,无手写清单、无文档同步义务(2026-11-07 架构收窄批改版;原手写清单
# 对文档核录的做法废弃——头注计数与脚本字面已实测漂移 1 符号,手写必漂)。
# 扫描面:$DIR_CORE(含 settlement/,B1 文件集在其中)。
# 判据:文件内出现 `.符号(` 调用形态(符号后紧跟括号,天然词边界;`.my_grant(` 不误伤 `grant`)
# 且该文件 ∉ B1 文件集 → 红。排除注释行与字符串字面量(口径仿 S1/S2/S8 的 grep 排除手法)。
@@ -379,7 +378,7 @@ fi
# (文件被改名=豁免名单失效,宁可红不可静默放行)。
if require_scan_face "S7" "$DIR_CORE"; then
# B1 域文件集(迭代 46 卡A 增补 battle_refresh_flow——换图刷新流程件=C13 拆分边界的 B1 域拆件,
# 承载旧资源退役/装板编排调用;architecture.md「模块归属」BattleRefreshFlow 行随反哺点落文)
# 承载旧资源退役/装板编排调用;清单单点=本脚本)
S7_B1_SET="$B1_SET" # 与 S8 同一单点清单(原第二份抄本,2026-11-03 并点——两份靠人工同步必漂)
S7_MISS=0
for f in $S7_B1_SET game/core/unit_container.gd game/core/status_container.gd game/core/reward_economy.gd; do
@@ -406,13 +405,27 @@ else
"
done < <(find "$DIR_CORE" -name '*.gd' 2>/dev/null | sort)
}
# 符号清单(2026-10-25 对 architecture.md「模块归属」表核录;改符号先改文档再改此处)
s7_scan B2 game/core/unit_container.gd \
'spawn|query_alive|rollable_skills|special_skills|check_skill_unlocks|set_pending_action|pending_action|basic_streak|commit_action|tick_cooldowns|apply_damage|heal|default_target|despawn_all'
s7_scan B3 game/core/status_container.gd \
'notify_battle_start|apply_effect|query_active|decay_round|notify_battle_end'
s7_scan B4 game/core/reward_economy.gd \
'roll_board|query_identity|query_full_snapshot|grant|grant_clear|retire_mapping|polarity_row|phase'
# 符号清单自动提取(2026-11-07 架构收窄批):公开方法面=非下划线 func 名(含 static func),
# 清单=代码现状;is_mapped 为批量工具专用投影、不在暴露面(沿用原手写清单口径,提取后剔除)
extract_public_funcs() {
grep -oE '^[[:space:]]*(static )?func [a-z][a-z_0-9]*' "$1" 2>/dev/null \
| sed 's/^[[:space:]]*\(static \)\?func //' | paste -sd'|' -
}
B2_SYMS=$(extract_public_funcs game/core/unit_container.gd)
B3_SYMS=$(extract_public_funcs game/core/status_container.gd)
B4_SYMS=$(extract_public_funcs game/core/reward_economy.gd | sed 's/|is_mapped//')
# 提取非空自检(防空转:提取失败=空 pattern,grep 零命中恒绿)
S7_EXTRACT_MISS=0
for v in "$B2_SYMS" "$B3_SYMS" "$B4_SYMS"; do
if [ -z "$v" ]; then
echo "[static_scan] S7 FAIL: 公开方法面提取为空——三族文件缺位或无公开 func(提取机制静默空转)"
S7_EXTRACT_MISS=1
fi
done
[ "$S7_EXTRACT_MISS" -eq 1 ] && fail=1
[ -n "$B2_SYMS" ] && s7_scan B2 game/core/unit_container.gd "$B2_SYMS"
[ -n "$B3_SYMS" ] && s7_scan B3 game/core/status_container.gd "$B3_SYMS"
[ -n "$B4_SYMS" ] && s7_scan B4 game/core/reward_economy.gd "$B4_SYMS"
if [ -n "$S7_HITS" ]; then
echo "[static_scan] S7 FAIL: B2/B3/B4 API 符号越出 B1 文件集命中 $(printf '%s' "$S7_HITS" | grep -c .) 处(编排唯一性,B1 AC-43)"
printf '%s' "$S7_HITS"
+8 -8
View File
@@ -34,7 +34,7 @@ func _initialize() -> void:
# (自 game/smoke/smoke_all.gd 剪切迁入——断言 label 逐字原样;G12 两套调用不随迁,
# 归 source_guard_test.gd,断言迁移批 B G1)
func _split_tail() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
await process_frame
await process_frame
var autoload_ok: bool = get_root().get_node_or_null("/root/ConfigDB") != null and get_root().get_node_or_null("/root/SaveManager") != null
@@ -54,7 +54,7 @@ func _split_tail() -> void:
# ---- 三屏场景化断言(V2——独立实例化+关键节点在位+组合根树序/z 序保序)----
# (自 game/smoke/smoke_all.gd 剪切迁入——label 与 cond 逐字原样,断言迁移批 B G1)
func _smoke_scene_split() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
print("--- 迭代27 T1 三屏场景化(V2:屏壳独立实例化+组合根装配保序)---")
# ① 三屏壳独立实例化(F6 等价——headless 直挂;视图代码构建在屏壳 _ready)
var menu: Node = load("res://presentation/scenes/menu_screen.tscn").instantiate()
@@ -151,7 +151,7 @@ func _count_visible_buttons(node: Node) -> int:
# ---- boot 链路时序后置条件(启动态六断言;只立行为可判断言,不造假时序)----
func _smoke_boot_chain() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
print("--- 迭代28 T2 boot 链路时序后置条件(V2:六断言)---")
var main: Node = load("res://presentation/main.tscn").instantiate()
root.add_child(main)
@@ -216,7 +216,7 @@ func _smoke_boot_chain() -> void:
func _smoke_run_lifecycle() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
print("--- 迭代27 T1 会话物化(V3:两连局复位+脏状态不残留)---")
var main: Node = load("res://presentation/main.tscn").instantiate()
root.add_child(main)
@@ -274,7 +274,7 @@ func _smoke_run_lifecycle() -> void:
# ---- d6/d7 批量模拟互斥守卫(wd6/wd7——白盒置态直调入口,以守卫早退的可见后果为断言面)----
func _smoke_sim_guards() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
print("--- 卡① T2:批量模拟互斥两守卫(d6/d7)---")
var main: Node = load("res://presentation/main.tscn").instantiate()
root.add_child(main)
@@ -348,7 +348,7 @@ func _face_snapshot(main: Node) -> Dictionary:
# 阶段面断言:adv/run/set=alive|null|both(会话三面存活态);恒活面每阶段必查;
# 现值回调与 run_scope 成员必须同源(回调链无悬挂铁证);extra=阶段附加判据。
func _stage_faces(main: Node, tag: String, adv: String, run: String, set: String, extra: Dictionary = {}) -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var snap := _face_snapshot(main)
var bad := ""
for k in ["bonus_meta", "bonus_save", "camp_meta", "camp_save", "rp_log", "rp_stage", "rp_board_zone", "rp_fx", "rp_board"]:
@@ -417,7 +417,7 @@ func _face_liveness(name: String, v, want: String) -> String:
#(level_started→choices_presented 同步到达,无可观测中间态)——合并为 V3b 一处断言。
# (自 game/smoke/smoke_all.gd 剪切迁入——label 与 cond 逐字原样,断言迁移批 B G1)
func _smoke_lifecycle_matrix() -> void:
# oracle: design/gdd/systems/冒险循环/冒险结构.md:284 (生命周期矩阵——三关序列组)
# 判据出处: design/gdd/systems/冒险循环/冒险结构.md:284 (生命周期矩阵——三关序列组)
print("--- 迭代28 T3 生命周期存活性矩阵(V3:九阶段×五引用面)---")
var main: Node = load("res://presentation/main.tscn").instantiate()
root.add_child(main)
@@ -608,7 +608,7 @@ func _smoke_lifecycle_matrix() -> void:
func _smoke_camp_hud_band() -> void:
# oracle: design/ux/camp.md:41 (营地顶栏栏体定值与落座——几何/色值/吃点击五钉组)
# 判据出处: design/ux/camp.md:41 (营地顶栏栏体定值与落座——几何/色值/吃点击五钉组)
print("--- 营地交互修复批 T3 顶栏栏体(R5:色值/铺满/不吃点击/首子件/徽章落座)---")
var main: Node = load("res://presentation/main.tscn").instantiate()
root.add_child(main)
@@ -8,13 +8,13 @@ const Kit := preload("res://tests/unit/test_kit.gd")
func _init() -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:293 Boss 节点开战行集宿主(装载组)
# 判据出处: design/gdd/systems/战斗循环/敌人系统.md:293 Boss 节点开战行集宿主(装载组)
_test_boss_node_spawns_boss_rowset()
quit(Kit.finish("boss_gate_test"))
func _test_boss_node_spawns_boss_rowset() -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:295 开战生成按遭遇坐标取行集(Boss 行集组)
# 判据出处: design/gdd/systems/战斗循环/敌人系统.md:295 开战生成按遭遇坐标取行集(Boss 行集组)
var db = load("res://core/config_db.gd").new()
var load_ok: bool = db.load_tables("res://data/tables", true) and db.diagnostics_count("error") == 0
Kit.check("前置:表加载零 error", load_ok)
@@ -23,7 +23,7 @@ var _rows: Array = [] # 当前 run 的行协议收集(_on_row 绑定法—
func _init() -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 伤害单据编排宿主(装载组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:360 伤害单据编排宿主(装载组)
var db = ConfigDBScript.new()
var load_ok: bool = db.load_tables("res://data/tables", true)
Kit.check("前置:表加载零 error", load_ok and db.diagnostics_count("error") == 0)
@@ -71,7 +71,7 @@ func _row_by_display_prefix(prefix: String) -> Dictionary:
# ---- 五配方一次全流程 + 双跑逐位复现 ----
func _run_recipe_sequence(db) -> Array:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (五段全序——配方序列组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:362 (五段全序——配方序列组)
_rows = []
var h = _harness(db)
var units = h.units
@@ -178,7 +178,7 @@ func _run_recipe_sequence(db) -> Array:
func _test_five_recipes_and_replay(db) -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:363 (同种子回放逐行相同组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:363 (同种子回放逐行相同组)
var run1: Array = _run_recipe_sequence(db)
Kit.check("五配方序列产出 5 行伤害行", run1.size() == 5)
var run2: Array = _run_recipe_sequence(db)
@@ -188,7 +188,7 @@ func _test_five_recipes_and_replay(db) -> void:
# ---- A 模式(开单即结——回切位) ----
func _test_policy_immediate_mode(db) -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (结算策略即时面组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:362 (结算策略即时面组)
_rows = []
var h = _harness(db)
if not h.spawn_ok:
@@ -204,7 +204,7 @@ func _test_policy_immediate_mode(db) -> void:
# ---- 来源敌死亡:队列中未结单据作废(死亡即不动手) ----
func _test_source_death_void(db) -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:133 (摇定后兑现前被杀本拍作废组)
# 判据出处: design/gdd/systems/战斗循环/敌人系统.md:133 (摇定后兑现前被杀本拍作废组)
_rows = []
var h = _harness(db)
if not h.spawn_ok:
@@ -238,7 +238,7 @@ func _unit_fixture() -> Dictionary:
func _test_identical_content_dual_enemy(db: ConfigDb) -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:303 (双敌同内容顺位组)
# 判据出处: design/gdd/systems/战斗循环/敌人系统.md:303 (双敌同内容顺位组)
var a := _unit_fixture()
var b := _unit_fixture()
Kit.check("前置:双敌字典内容全同且非同一实例", a == b and not is_same(a, b))
@@ -268,7 +268,7 @@ func _test_identical_content_dual_enemy(db: ConfigDb) -> void:
# d12 产发端增键:目标身份随行/随载荷下发(同名敌按 id 精确解析的产发半)
func _test_target_id_payload(db: ConfigDb) -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
_rows = []
var h = _harness(db)
if not h.spawn_ok:
@@ -291,7 +291,7 @@ func _test_target_id_payload(db: ConfigDb) -> void:
func _test_requires_gate_and_zero(db) -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
_rows = []
var h = _harness(db)
if not h.spawn_ok:
@@ -318,7 +318,7 @@ func _test_requires_gate_and_zero(db) -> void:
# ---- 主角倒下:flush 余单全部作废(_halted 终止) ----
func _test_hero_death_halt(db) -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
_rows = []
var h = _harness(db)
if not h.spawn_ok:
@@ -336,7 +336,7 @@ func _test_hero_death_halt(db) -> void:
# ---- AC 合流:override 入参 / 表值兜底 / ac 型状态叠层(不入池) ----
func _test_ac_merge(db) -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
_rows = []
var h = _harness(db)
if not h.spawn_ok:
@@ -364,7 +364,7 @@ func _test_ac_merge(db) -> void:
# ---- 池乘区收口:池内加算 → clamp;ac 型不入池 ----
func _test_pool_clamp(db) -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
_rows = []
var h = _harness(db)
if not h.spawn_ok:
@@ -422,7 +422,7 @@ func _test_pool_clamp(db) -> void:
# ---- 个体池读数 / 个体 AC 合流(增益状态 v1.5——敌方状态个体化批)----
func _test_pool_consumption_per_unit(db) -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var h = _harness(db, 3) # depth 3 = 混编行集(熊地精 1 + 地精 1–2,最少 2 只——个体对照面需 ≥2 敌)
if not h.spawn_ok:
return
@@ -442,7 +442,7 @@ func _test_pool_consumption_per_unit(db) -> void:
func _test_defender_ac_per_unit(db) -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var h = _harness(db, 3)
if not h.spawn_ok:
return
@@ -12,7 +12,7 @@ var db
func _init() -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (伤害公式宿主——装载组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:360 (伤害公式宿主——装载组)
db = ConfigDBScript.new()
var load_ok: bool = db.load_tables("res://data/tables", true)
Kit.check("前置:settlement 表加载零 error", load_ok and db.diagnostics_count("error") == 0)
@@ -31,7 +31,7 @@ func _floor_val() -> int:
# ---- F2 乘区:池内加算 → 池间乘算 → floor(单一取整规则)----
func _test_pool_damage() -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (池伤害公式组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:360 (池伤害公式组)
var d := _floor_val() + 9 # 表值推导的任意正探针量(非玩法行值)
Kit.check("中性双池 (0,0) 原样透传", DamageMathScript.pool_damage(d, 0, 0) == d)
Kit.check("负敌方池压低不增高", DamageMathScript.pool_damage(d, 0, int(db.combat.settlement.core.pool_clamp_lo)) <= d)
@@ -44,7 +44,7 @@ func _test_pool_damage() -> void:
# ---- 克制分量:空桶不吃修正恒 0;落地下限保底([1,+∞) 全来源)----
func _test_counter_component() -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (计数器分量组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:360 (计数器分量组)
var floor_v := _floor_val()
Kit.check("空桶(点数≤0)恒 0 不吃修正", DamageMathScript.counter_component(0, floor_v, floor_v) == 0
and DamageMathScript.counter_component(-1, 5, floor_v) == 0)
@@ -56,7 +56,7 @@ func _test_counter_component() -> void:
# ---- 掷点折扣查档(纯函数):满阈值恒全额;档位边界/末档语义与表数组一致 ----
func _test_roll_discount_percent() -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (乘区折扣组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:360 (乘区折扣组)
var threshold := 10 # 查档探针阈值(夹具值——档位参数本身全部来自表)
var bounds: Array = db.combat.settlement.damage_roll.roll_tier_bounds
var discounts: Array = db.combat.settlement.damage_roll.roll_tier_discounts
@@ -81,7 +81,7 @@ func _test_roll_discount_percent() -> void:
# ---- 吃掷点分量算术:折扣→(暴击)→floor→+克制修正(单一取整链)----
func _test_rolled_component() -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (掷点分量组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:360 (掷点分量组)
var base := float(_floor_val() + 7) # 表值推导探针基线
var crit_mult: int = int(db.combat.settlement.damage_roll.crit_multiplier)
Kit.check("满额折扣无暴击 = floor(基线) + 修正 0", DamageMathScript.rolled_component(base, 100, false, crit_mult, 0) == int(floor(base)))
@@ -19,7 +19,7 @@ var _db
func _init() -> void:
# oracle: design/engineering/architecture/architecture.md:144 (效果端口宿主——装载组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·功能层」battle_orchestrator 行(域文件集含 EffectPort) (效果端口宿主——装载组)
_db = ConfigDBScript.new()
var load_ok: bool = _db.load_tables("res://data/tables", true)
Kit.check("前置:表加载零 error", load_ok and _db.diagnostics_count("error") == 0)
@@ -54,7 +54,7 @@ func _hero_attack_value(rows: Array) -> int:
# ---- V1 端口单实例与真实发射路由 ----
func _smoke_effect_port_wiring() -> void:
# oracle: design/engineering/architecture/architecture.md:144 (端口单实例与六 Callable 路由组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·功能层」battle_orchestrator 行(域文件集含 EffectPort) (端口单实例与六 Callable 路由组)
print("--- 效果端口:装配链单实例+真实发射路由 ---")
# 充能跑(蓄力周转 charge_rotation 在册——attack_commit 订阅行 attack_bonus per 蓄力周转计数器)
var orch_c = _orch(TEST_SEED)
@@ -88,7 +88,7 @@ func _smoke_effect_port_wiring() -> void:
# ---- V2 业务类零引擎直连(源码契约锚——theme_projection_test 源码锚惯例)----
func _test_business_classes_engine_free() -> void:
# oracle: design/engineering/architecture/architecture.md:144 (业务类零引擎直连组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·功能层」battle_orchestrator 行(域文件集含 EffectPort) (业务类零引擎直连组)
print("--- 效果端口:业务类零引擎直连源码锚 ---")
for path in ["res://core/damage_controller.gd", "res://core/action_economy.gd"]:
var src := FileAccess.get_file_as_string(path)
@@ -100,7 +100,7 @@ func _test_business_classes_engine_free() -> void:
# ---- V3 伤害域事实面闭集(合成行两对照——damage_taken 真实单据流)----
func _test_biz_fact_face_closed() -> void:
# oracle: design/engineering/architecture/architecture.md:144 (闭面四键 biz_fact 组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·功能层」battle_orchestrator 行(域文件集含 EffectPort) (闭面四键 biz_fact 组)
print("--- 效果端口:业务发射事实面闭集 ---")
var orch = _orch(TEST_SEED)
Kit.check("V3 begin_battle 受理", orch.begin_battle(_enc([])))
@@ -11,7 +11,7 @@ const Kit := preload("res://tests/unit/test_kit.gd")
func _init() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 (扩展行必载 effect_id——加载致命组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:199 (扩展行必载 effect_id——加载致命组)
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
push_error("[extended_row_violation_test] 表加载失败")
@@ -154,7 +154,7 @@ func _seal_direct_with_expansion(orch) -> Vector2i:
# 四条真实路径(敌方攻击/危险格/错误封印/地形)逐路驱动:每条至少一张主角承伤单据在案,
# 且承伤计数器 == 主角承伤单据行数(每单据恰发射一次的全量判据——重复发射即计数器>行数)。
func test_all_hero_damage_paths_emit_once() -> void:
# oracle: design/gdd/systems/冒险循环/奇物.md:465 (换图生存:全局固定减伤全伤害路径恰一次组)
# 判据出处: design/gdd/systems/冒险循环/奇物.md:465 (换图生存:全局固定减伤全伤害路径恰一次组)
print("--- V5 全局 damage_taken:四路径逐路恰一次 ---")
# ① 敌方攻击路径(Boss 战零挖掘连停三轮——干净样本:零地形零资源单据;多轮保证伤害技在案)
var orch1 = _orch(46001)
@@ -224,7 +224,7 @@ func _plan_seal(orch, target: Vector2i) -> Dictionary:
# 同一伤害不得出现两次消费(重复发射=计数器>行数):敌方路径与地形路径两样本逐位核对——
# 地形单据带 hero_damage_consumed 标记,战斗段末 _resolve_board_flat_hero 跳过二次发射由此钉住。
func test_no_duplicate_damage_taken_event() -> void:
# oracle: design/gdd/systems/冒险循环/奇物.md:465 (受击事件零重复组)
# 判据出处: design/gdd/systems/冒险循环/奇物.md:465 (受击事件零重复组)
print("--- V5 全局 damage_taken:无重复消费 ---")
var orch1 = _orch(46011)
var rows1 := _collect_hero_rows(orch1)
@@ -266,7 +266,7 @@ func _auto_dig(orch) -> void:
# V2 矿脉储能:受理的付费挖格 RAP 恰 +1、AP 按表扣、板资源不变
func test_reserve_on_accepted_dig() -> void:
# oracle: design/gdd/systems/排雷循环/行动经济.md:256 (换图基座:挖格产 RAP 组)
# 判据出处: design/gdd/systems/排雷循环/行动经济.md:256 (换图基座:挖格产 RAP 组)
print("--- V2 矿脉储能:付费挖格产 RAP ---")
var orch = _orch(46021)
Kit.check("V2 前置:战斗受理", orch.begin_battle(_enc(["vein_reserve"])))
@@ -304,7 +304,7 @@ func _drain_round_rap_accounting(orch) -> bool:
# V3 省力周转:折价门槛与扣费(can 与执行同源;base 8→扣 4)
func test_refresh_discount_gate_and_charge() -> void:
# oracle: design/gdd/systems/排雷循环/行动经济.md:256 (刷新折扣门控与充能组)
# 判据出处: design/gdd/systems/排雷循环/行动经济.md:256 (刷新折扣门控与充能组)
print("--- V3 省力周转:折价门槛与扣费 ---")
var orch = _orch(46022)
Kit.check("V3 前置:战斗受理", orch.begin_battle(_enc(["easy_swap"])))
@@ -326,7 +326,7 @@ func test_refresh_discount_gate_and_charge() -> void:
# V3 双基座叠加:挖格产 RAP 与刷新折价同场生效
func test_double_base_stack() -> void:
# oracle: design/gdd/systems/冒险循环/奇物.md:465 (双基座叠加组)
# 判据出处: design/gdd/systems/冒险循环/奇物.md:465 (双基座叠加组)
print("--- V3 双基座叠加 ---")
var orch = _orch(46023)
Kit.check("V3 前置:战斗受理", orch.begin_battle(_enc(["vein_reserve", "easy_swap"])))
@@ -343,7 +343,7 @@ func test_double_base_stack() -> void:
# V12(refresh_feats 侧):无专长基线——挖格零产 RAP、刷新按表基线扣、AP 保留、上限照旧
func test_no_feats_baseline() -> void:
# oracle: design/gdd/systems/冒险循环/奇物.md:465 (零专长基线组)
# 判据出处: design/gdd/systems/冒险循环/奇物.md:465 (零专长基线组)
print("--- V12 无专长基线(refresh_feats 侧) ---")
var orch = _orch(46024)
Kit.check("V12 前置:战斗受理", orch.begin_battle(_enc([])))
@@ -379,7 +379,7 @@ func _br_count(orch) -> int:
# V4 成功事实只在新板就位后发射:成功恰一次;非 Active/每轮上限/RAP 不足三拒面全零事实零换图
func test_board_refresh_fact_only_on_success() -> void:
# oracle: design/gdd/systems/排雷循环/行动经济.md:256 (弃图刷新事实面仅成功态组)
# 判据出处: design/gdd/systems/排雷循环/行动经济.md:256 (弃图刷新事实面仅成功态组)
print("--- V4 成功刷新事实:拒绝零事实 ---")
_install_br_probe()
var orch = _orch(46031)
@@ -414,7 +414,7 @@ func test_board_refresh_fact_only_on_success() -> void:
# V10 矿道红利:每次成功刷新 escrow 恰 +1 且 fx_grant_gp 行在案;拒绝零变化
func test_mine_profit_gp() -> void:
# oracle: design/gdd/systems/冒险循环/奇物.md:465 (换图资源:刷新固定 GP 收益组)
# 判据出处: design/gdd/systems/冒险循环/奇物.md:465 (换图资源:刷新固定 GP 收益组)
print("--- V10 矿道红利:刷新固定 GP ---")
var orch = _orch(46032)
var gp_rows := {"n": 0}
@@ -461,7 +461,7 @@ func _damage_rows_by_source(rows: Array, source: String) -> Array:
# V6 石层缓冲:全局固定减伤(危险格 pierce 8→6 两档钉死:单刷减 2·同轮多刷不叠加·未刷新轮不减;
# 地形路径减后值=max(下限, effect_value−2)——含下限保持;敌方路径照常单次发射)
func test_stone_mitigation_all_damage() -> void:
# oracle: design/gdd/systems/冒险循环/奇物.md:465 (全局减伤全覆盖组)
# 判据出处: design/gdd/systems/冒险循环/奇物.md:465 (全局减伤全覆盖组)
print("--- V6 石层缓冲:全局固定减伤 ---")
# S1 同轮双刷 → 危险格 pierce 8 减 2=6(叠加即 4——多刷不增钉死)
var orch1 = _orch(46041)
@@ -517,7 +517,7 @@ func test_stone_mitigation_all_damage() -> void:
# V9 坑道小憩:轮末合计回复(白盒预置承载上限 3——计数器直改属数据面注入;
# 自然累计 2 与满血未生效两形态分战场钉死)
func test_rest_rotation_cap_and_clear() -> void:
# oracle: design/gdd/systems/冒险循环/奇物.md:465 (轮末恢复 cap 与清账组)
# 判据出处: design/gdd/systems/冒险循环/奇物.md:465 (轮末恢复 cap 与清账组)
print("--- V9 坑道小憩:轮末恢复 ---")
# R1 白盒预置 3 层 + 刷新闸(when<3 拒第 4 层)→ 停手回复 min(3, 缺血)——护甲降档保缺血 ≥3
var orch1 = _orch(46051)
@@ -575,7 +575,7 @@ func test_rest_rotation_cap_and_clear() -> void:
# V8 蓄力周转:当轮攻击消费全部层数(上限 3 闸=白盒预置;战斗新建自然清空)
func test_charge_layers_consumed_same_round() -> void:
# oracle: design/gdd/systems/冒险循环/奇物.md:465 (充能当轮消耗组)
# 判据出处: design/gdd/systems/冒险循环/奇物.md:465 (充能当轮消耗组)
print("--- V8 蓄力周转:攻击消费层 ---")
var orch1 = _orch(46061)
var rows1 := _collect_damage_rows(orch1)
@@ -602,7 +602,7 @@ func test_charge_layers_consumed_same_round() -> void:
# V7 崩壁冲击:延迟群伤单(刷新时点零伤害行——战斗段末按存活目标结账)
func test_blast_defers_to_combat_segment() -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (刷新群伤让位战斗段组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:362 (刷新群伤让位战斗段组)
print("--- V7 崩壁冲击:延迟群伤 ---")
var orch = _orch(46071)
var rows := _collect_damage_rows(orch)
@@ -621,7 +621,7 @@ func test_blast_defers_to_combat_segment() -> void:
# 七件 validate/在册/稀有度 cap 对表/跨表计数器引用闭合/desc 禁词
func test_seven_feats_validate_and_pool() -> void:
# oracle: design/gdd/systems/冒险循环/奇物.md:465 (换图系七件校验与池组)
# 判据出处: design/gdd/systems/冒险循环/奇物.md:465 (换图系七件校验与池组)
print("--- V11 七件数据与池 ---")
var seven := {"vein_reserve": "excellent", "easy_swap": "excellent", "wall_blast": "excellent",
"charge_rotation": "excellent", "stone_mitigation": "common", "tunnel_rest": "common", "mine_profit": "rare"}
@@ -19,7 +19,7 @@ var _db
func _init() -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (掷点与乘区宿主——装载组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:360 (掷点与乘区宿主——装载组)
_db = ConfigDBScript.new()
# 表加载失败不中止(承 G2/G3 同款:表损坏的红落在具体断言上——
# 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评)
@@ -44,7 +44,7 @@ func _init() -> void:
# 受理后不变量(encounter 受理后只读)。攻击单 base_value 贯通由 _smoke_damage_types 的
# hero_atk_val=100 全链路行值承载(同夹具既有覆盖——派生读在攻击链上工作即行值随 override 走)----
func _smoke_hero_atk_derived() -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (英雄攻击派生组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:360 (英雄攻击派生组)
var rng_a = load("res://core/rng_service.gd").new()
rng_a.set_master_seed(4242)
var s_a = load("res://core/battle_orchestrator.gd").new(_db, rng_a)
@@ -67,7 +67,7 @@ func _smoke_hero_atk_derived() -> void:
# → heavy 伤害 == light −1、ward == light +1(要求基线伤害 ≥ 2——地板 1 会吞 −1 位移)。
# 承伤位移红线(卡片 §8 L3):全局位移只允许 ±1 量级——非此量级 = 矩阵消费面有未盘点的第二落点(停机)。
func _smoke_enemy_attack_type() -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:295 (敌攻击类型行集组)
# 判据出处: design/gdd/systems/战斗循环/敌人系统.md:295 (敌攻击类型行集组)
print("--- 敌方攻击类型轴(迭代 24 N3)---")
# ① 表列:20 行全填 normal(N3 全填不做差异化——Q9)
var at_ok := true
@@ -126,7 +126,7 @@ func _smoke_enemy_attack_type() -> void:
# ---- 护甲等级掷点·行为面(折扣档分布 / nat20 暴击 / 伤害下限 / 棋盘来源必中)----
func _smoke_armor_roll() -> void:
# oracle: design/gdd/systems/战斗循环/增益状态.md:254 (护甲掷点与叠层合并组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:254 (护甲掷点与叠层合并组)
print("--- 护甲等级掷点(查档纯函数 + 暴击 + 伤害下限 + AC12 分布 + 棋盘必中)---")
# ① 查档纯函数:全掷点域逐位对照(AC10 / AC12)+ EV 锚(AC10 期望减免 21.25% —— registry 参考数)
var expect_ac10 := {100: 11, 75: 3, 50: 4, 25: 2} # 掷 10-20 全额 / 7-9 / 3-6 / 1-2
@@ -345,7 +345,7 @@ func _auto_play_round(settlement, out: Dictionary) -> void:
# 基线构成:基线 100——hero 攻击类型=normal(锻造工坊配置默认值,
# hero.tres 无两轴字段)对轻甲中性 0 修正(目标 = L1 地精轻甲;AC 0 恒全额非暴击种子)。
func _smoke_pool_wiring() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
print("--- 主角乘区接线 + 硬化折半方向 ---")
var base_row := _hero_attack_row_with({}, 100, 20260918)
var up_row := _hero_attack_row_with({"effects": ["attack_up"]}, 100, 20260918)
@@ -387,7 +387,7 @@ func _hero_attack_row_with(opts: Dictionary, hero_atk_val: int, seed_val: int) -
# ---- 攻击类型 × 护甲克制 + 标错惩罚冒烟(护甲快照/克制矩阵/充能/反噬三选一)----
func _smoke_damage_types() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
print("--- 攻击类型 × 护甲克制 + 标错惩罚 ---")
# ① 护甲类型快照传播(spawn 行值 → 单位字典)
var enemy_system = load("res://core/unit_container.gd").new(_db)
@@ -482,7 +482,7 @@ func _find_seal_target(minesweeper, want_mine: bool) -> Dictionary:
# d_tick = max(damage_floor, pool_damage(floor(基线×档位%), 0, 0)),档位读数随行重建可归因)----
func _smoke_attack_chain_fresh_pool() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
print("--- 战斗结算·fresh 池攻击链回归(Bug#2·掷点口径)---")
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(20260913)
@@ -22,7 +22,7 @@ var _db
func _init() -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (轮内流与技能池宿主——装载组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:360 (轮内流与技能池宿主——装载组)
_db = ConfigDBScript.new()
# 表加载失败不中止(承 G2/G3/G4/G5 同款:表损坏的红落在具体断言上——
# 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评)
@@ -42,13 +42,14 @@ func _init() -> void:
_smoke_counter_buckets()
_smoke_extended_rows()
_smoke_enemy_buff_targets_self()
_check_defensive_stance_values()
quit(Kit.finish("round_flow_skill_pool_test"))
# ---- 完整战斗回放(自动玩家,确定性)——回放断言:同种子两遍步骤流逐行相同 ----
func _smoke_full_battle(L: int, D: int, label: String) -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (全战斗回放——五段全序组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:362 (全战斗回放——五段全序组)
print("--- 完整战斗回放:%s ---" % label)
var run1 = _run_battle(L, D)
var run2 = _run_battle(L, D)
@@ -83,7 +84,7 @@ func _smoke_full_battle(L: int, D: int, label: String) -> void:
func _run_battle(L: int, D: int, axes: Dictionary = {}) -> Dictionary:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (战斗驱动组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:362 (战斗驱动组)
# 每遍全新 RngService(同 master 种子)——回放断言的隔离前提(地图战斗资源 AC"两套全新 RngService+地图战斗资源"同款)
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(42)
@@ -148,7 +149,7 @@ func _auto_play_round(settlement, out: Dictionary) -> void:
# ---- 行动经济·轮内刷新冒烟(方案 A:Active 相位当场换图继续挖)----
func _smoke_active_refresh() -> void:
# oracle: design/gdd/systems/排雷循环/行动经济.md:262 (停手转存储备与轮内刷新组)
# 判据出处: design/gdd/systems/排雷循环/行动经济.md:262 (停手转存储备与轮内刷新组)
print("--- 行动经济·轮内刷新(方案 A)---")
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(20260912)
@@ -255,7 +256,7 @@ func _active_refresh_replay() -> Array:
# ---- 遭遇随机化冒烟(难度曲线与Boss Core Rule 8:区间骰/encounter 流/可回放/空表定值路径)----
func _smoke_encounter_roll() -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:296 (遭遇区间骰——摇号确定性组)
# 判据出处: design/gdd/systems/战斗循环/敌人系统.md:296 (遭遇区间骰——摇号确定性组)
print("--- 遭遇随机化(unit_count 区间骰)---")
var counts: Array = []
for i in range(20):
@@ -390,7 +391,7 @@ func _encounter_replay(seed_val: int) -> Array:
# 五段全序断言(战斗结算 验收"五段全序"+ 行型封闭 10 类):逐轮行相位序 = 授予 → 增益/减益 → 战斗 → 衰减 → 判定。
func _smoke_settlement_segments() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
print("--- 五段全序 + 行型封闭 ---")
Kit.check("segment_order 五段冻结在位(表值 = 拍板序恒等)",
Array(_db.combat.settlement.segment_order) == SettlementTable.FROZEN_SEGMENT_ORDER
@@ -509,7 +510,7 @@ func _smoke_skill_pool() -> void:
# ① 数据面:技能表行集 + 普攻锁死 + 建议池行值 + 空池不可能(结构断言)
func _smoke_skill_pool_data() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
Kit.check("技能表十六行(普攻/突刺/蓄力/重击/硬化 + 撕咬/毒爆/蛛网束缚 + 大地脉动 + 震地怒击/遏毒/岩崩 + 毒吐/盾击——前三批 + Boss 半血解锁技三行〔迭代14④ T5〕+ 普通敌错峰两行〔迭代 24 N2〕+ 触发条件批示范两行〔孤注守势/趁隙偷袭〕)", _db.combat.skill.rows.size() == 16)
var basic: Dictionary = _db.combat.skill.row_by_id("attack")
Kit.check("普攻行:倍率恒 1 · 冷却恒 0(域校验锁死——池恒非空的结构前提)",
@@ -726,7 +727,7 @@ func _fresh_rng(seed_val: int):
# 单敌轨迹不独立复放——轨迹取数与其它流交错,无法脱离全序重建;
# 同种子全战斗逐位复现由 ② 全对拍承载,单点定位归事件日志。
func _smoke_skill_pool_replay_independence() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var seed_val := -1
for s in range(1, 60):
var seqs := _record_pool_trajectories(s, 1)
@@ -780,7 +781,7 @@ func _record_pool_trajectories(seed_val: int, rounds: int) -> Dictionary:
# 2026-10-28 重锚 [0.82, 1.02](照酋长先例 ±0.10 绝对幅度,中值取理论稳态 0.92;旧带 [1.00,1.20] 系触发器前
# 唯一特殊技=突刺的池结构下登记,随池构成换代作废;撤销证明=带宽回退旧值本断言复红)→ 宽带登记。
func _smoke_skill_pool_ev_anchor() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var bands := {"goblin": [0.82, 1.02], "bugbear": [0.97, 1.03], "ogre_chief": [1.10, 1.30]} # 地精重锚 2026-10-28(触发条件批孤注守势入池稀释,无情境口径实测 0.9148;EV 锚注 enemy.tres)· 酋长重锚(震地怒击半血解锁入池,含闸长期速率上移:实测 1.1948;EV 锚注 enemy.tres)
var species := ["goblin", "bugbear", "ogre_chief"]
var rng = load("res://core/rng_service.gd").new()
@@ -836,7 +837,7 @@ func _smoke_skill_pool_ev_anchor() -> void:
# ⑤ 死亡打断作废(摇定后、兑现前死亡 → 本拍技能作废、不动手、不进账)
func _smoke_skill_pool_death_void() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var void_seed := -1
for s in range(1, 400):
var rg = load("res://core/rng_service.gd").new()
@@ -876,7 +877,7 @@ func _smoke_skill_pool_death_void() -> void:
# ⑥ 加载校验拒绝(单表域校验 + 跨表池构成纪律)
func _smoke_skill_pool_validation_reject() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var skill_table = load("res://data/schemas/skill_table.gd").new()
var enemy_table = load("res://data/schemas/enemy_table.gd").new()
enemy_table.rows.assign([{"enemy_id": "x", "display_name": "x", "pool": [{"skill_id": "ghost", "weight": 1}]}])
@@ -935,7 +936,7 @@ func _has_err_category(errs: Array, category: String) -> bool:
# 停手轮驱动 (2,9)——零挖掘零封印 → 零池,伤害行可逐拍精确复算;种子 hunt 至少各命一粒毒爆伤害行
# 与蛛网束缚施放行;毒分量随开战掷定的攻击基线缩放(atk 区间 2–4 → rider 1–3)。
func _smoke_debuff_rider() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var venom_seed := -1
var venom_check_ok := true
var venom_rows_seen := 0
@@ -1006,7 +1007,7 @@ func _smoke_debuff_rider() -> void:
# ---- 天赋树六键行为冒烟(V10:开局姿态窗/危险直觉限次/财富之道聚散/守侧失误补偿;
# 引擎直构 + 编排授予段直注(_grant_seg._judge_seal=结算授予段封印判定单口))----
func _smoke_counter_buckets() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
print("--- 效果引擎无状态化四桶正向验收(卡② §9 V1/V6)---")
# V1——adventure 桶跨战持久:同一宿主字典下两战各真实 request_dig 一次,
# 断付费计数经 ctx 就地写入宿主字典并跨战累积(副本搬运态:读不到上一战值→第 2 战仍 1,此断言会红)。
@@ -1069,7 +1070,7 @@ func _smoke_counter_buckets() -> void:
# V6 形态先例同源:直入真实管线缝(_judge_seal 直调同款口径)。
func _smoke_extended_rows() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
print("--- 扩展行收敛:fx_reveal 兑付/落空 ---")
var RigS = load("res://core/battle_orchestrator.gd") # 局部装配别名(_smoke_counter_buckets 同款——跨函数不共享局部量)
var enc_fx = {"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40,
@@ -1115,7 +1116,7 @@ func _smoke_extended_rows() -> void:
# 蓄力表值 2 拍衰减后剩 1 拍仍在池)→ 逐敌读敌方输出池。判据=施放者与被他敌的池读数差。
func _smoke_enemy_buff_targets_self() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var emp_mag: int = int(_db.combat.status_effects.row_by_id("empower").magnitude)
var sc_script = load("res://core/status_container.gd")
var found_seed := -1
@@ -1156,3 +1157,69 @@ func _smoke_enemy_buff_targets_self() -> void:
break
Kit.check("蓄力施放种子可达(恰一次 cast=charge,seed=%d)" % found_seed, found_seed > 0)
Kit.check("敌方增益技自体落点:施放者 output 池 = 蓄力幅度(%d,实测 %d)、他敌恒 0〔个体化批 V5〕" % [emp_mag, carrier_pool], found_ok)
# ---- G6 状态行值断言(迭代 55「QA覆盖余量收口批」T6/V9——defensive_stance 生产行:减伤/持续/叠层)----
# 真实入口=战斗结算编排链(技能施放):begin_battle → 压低施法者 HP 触发 desperate_guard
#(self_hp_pct lte 35 非 latched)→ stop 推进结算 → 施放行 duration 与施法者个体状态池读数。
# 种子狩猎沿 _smoke_enemy_buff_targets_self 同款;叠层行为=容器级再授(roll_multiplier 先例形态)。
func _check_defensive_stance_values() -> void:
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var row: Dictionary = _db.combat.status_effects.row_by_id("defensive_stance")
Kit.check("defensive_stance 生产行值:magnitude=-25 percent/duration_settlements=2/accumulate max_stack=1〔QA余 V9〕",
int(row.get("magnitude", 0)) == -25 and String(row.get("unit", "")) == "percent"
and int(row.get("duration_settlements", 0)) == 2 and String(row.get("stack_policy", "")) == "accumulate"
and int(row.get("max_stack", 0)) == 1)
var sc_script = load("res://core/status_container.gd")
var found_seed := -1
var cast_dur := -1
var eff_val := 999
var keep_settlement = null
var keep_key := ""
for s in range(1, 401):
var rg = load("res://core/rng_service.gd").new()
rg.set_master_seed(s)
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rg)
var rows: Array = []
settlement.step_row.connect(func(r): rows.append(r))
if not settlement.begin_battle({"level_index": 1, "node_depth": 3, "hero_hp": int(_db.combat.hero.max_hp)}):
continue
for turn in range(4):
if settlement.battle_result != "":
break
for u in settlement._enemy_system.all_enemies():
u.hp = mini(int(u.hp), maxi(1, int(u.max_hp) * 35 / 100)) # 测试通道:压至触发阈内(弱形式登记)
settlement.minesweeper.settle_batch([{"action": "stop"}])
if settlement.battle_result != "":
break
var cast_seen := false
for r in rows:
if String(r.get("type", "")) == "cast" and String(r.get("skill_id", "")) == "desperate_guard":
cast_seen = true
cast_dur = int(r.get("duration", -1))
if cast_seen:
for u in settlement._enemy_system.all_enemies():
var okey: String = sc_script.owner_key_for(u)
for eff in settlement._status_effects.query_active(okey):
if String(eff.get("effect_id", "")) == "defensive_stance":
found_seed = s
eff_val = int(eff.get("effective_value", 999))
keep_settlement = settlement
keep_key = okey
if found_seed > 0:
break
if found_seed > 0:
break
Kit.check("种子狩猎命中(真实链施放 desperate_guard——401 种子内)〔QA余 V9〕", found_seed > 0)
Kit.check("减伤==生产行值:施法者个体池 effective_value==-25(L×m·max_stack1 单层)〔QA余 V9〕", eff_val == -25)
Kit.check("持续==生产行值:施放行 duration==2(buff_duration 表值)〔QA余 V9〕", cast_dur == 2)
# 叠层行为:max_stack=1 accumulate 下容器级再授(生产 dur=行值 2)→ effective 仍单层 -25 不叠加
if keep_settlement != null:
var src := {"source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "round_index": 1}
Kit.check("叠层再授受理", bool(keep_settlement._status_effects.apply_effect("defensive_stance", keep_key, src)))
var eff2 := 999
for eff in keep_settlement._status_effects.query_active(keep_key):
if String(eff.get("effect_id", "")) == "defensive_stance":
eff2 = int(eff.get("effective_value", 999))
Kit.check("叠层行为:max_stack=1 再授后 effective_value 仍==-25(不翻倍)〔QA余 V9〕", eff2 == -25)
@@ -22,7 +22,7 @@ var _db
func _init() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231 (封印与极性宿主——装载组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:231 (封印与极性宿主——装载组)
_db = ConfigDBScript.new()
# 表加载失败不中止(承 G2/G3/G4 同款:表损坏的红落在具体断言上——
# 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评)
@@ -40,13 +40,15 @@ func _init() -> void:
_smoke_seal_irreversible_quadrants()
_smoke_negative_dictionary()
_smoke_resource_surface()
_check_enemy_alert_production_row()
_check_ap_drain_production_row()
quit(Kit.finish("seal_polarity_test"))
# ---- 末点 AP 封印轮末结算(playtest 缺陷②回归:末点 AP 花在封印必须终结轮次进入下一轮)----
func _smoke_seal_ap_exhausted() -> void:
# oracle: design/gdd/systems/排雷循环/行动经济.md:260 (末点 AP 耗尽即收轮组)
# 判据出处: design/gdd/systems/排雷循环/行动经济.md:260 (末点 AP 耗尽即收轮组)
print("--- 排雷玩法·封印动作耗尽末点 AP 回归 ---")
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(20260911)
@@ -324,8 +326,8 @@ func _rows_at(rows: Array, source_class: String, cell) -> Array:
func _smoke_clear_reward_chain() -> void:
# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235 (清图奖励链——清图槽对象身份组)
# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235 (清图奖励链——清图槽对象身份组)
# 判据出处: design/gdd/systems/战斗循环/地图战斗资源.md:235 (清图奖励链——清图槽对象身份组)
# 判据出处: design/gdd/systems/战斗循环/地图战斗资源.md:235 (清图奖励链——清图槽对象身份组)
print("--- 清图奖励·双份兑付链(完美封印 / 零兑付 / 第二份被拒 / 高光记实)---")
# ① 真实流完美封印:标中一面真雷封印 + 零误标 + 清全图 → grant_clear 身份(clear_blast)双份真实兑付
var rows: Array = []
@@ -354,8 +356,8 @@ func _smoke_clear_reward_chain() -> void:
# 弱形式登记:正常战斗流下"授段时刻无存活敌"不可达——敌全灭当轮判定段即判胜并终止战斗,而清图批在判定段之前,
# 恒有存活敌;故本组直调编排私有面构造状态,只断言"路径存在 + 行为符合契约"(构造手法见各行注释)。
func _smoke_clear_reward_zero_paths() -> void:
# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:240 (无存活敌群伤记「奖励未生效」组)
# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:240 (无存活敌群伤记「奖励未生效」组)
# 判据出处: design/gdd/systems/战斗循环/地图战斗资源.md:240 (无存活敌群伤记「奖励未生效」组)
# 判据出处: design/gdd/systems/战斗循环/地图战斗资源.md:240 (无存活敌群伤记「奖励未生效」组)
# ②-a 首份零兑付判定(无存活敌)→ first_ok=false + 未生效行
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(20260915)
@@ -410,8 +412,8 @@ func _smoke_clear_reward_zero_paths() -> void:
# 轮内刷新 provenance 守卫:刷新换来的图同轮清图不发清图奖励(_board_from_midround_refresh 守卫)
func _smoke_refresh_provenance_guard() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:237 (换图重置:旧封印印记先判定后消失组)
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:237 (换图重置:旧封印印记先判定后消失组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:237 (换图重置:旧封印印记先判定后消失组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:237 (换图重置:旧封印印记先判定后消失组)
print("--- 轮内刷新 provenance 守卫(刷新图清图不发清图奖励)---")
var rows: Array = []
var run := _run_full_clear(20260916, true, rows)
@@ -438,8 +440,8 @@ func _smoke_refresh_provenance_guard() -> void:
func _smoke_polarity_routing() -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:366 (极性路由:正向只兑正向、负向只兑负向组)
# oracle: design/gdd/systems/战斗循环/战斗结算.md:366 (极性路由:正向只兑正向、负向只兑负向组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:366 (极性路由:正向只兑正向、负向只兑负向组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:366 (极性路由:正向只兑正向、负向只兑负向组)
print("--- 极性路由(挖安全格正向 / 挖危险格负向 / 标中正向 / 标空负向)---")
# 战斗随机值面随全局序漂移——构造种子按行为面扫描(谓词见 _polarity_probe_accepts)
var probe := {}
@@ -577,8 +579,8 @@ func _smoke_polarity_routing() -> void:
# 场景 B(②):标危险格 → 直接挖开 → 收轮。
# 场景 C(①③):标安全格 + 标危险格 → 不挖收轮 → 标空/标中判定照常。
func _smoke_seal_irreversible_quadrants() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231 (封印不可逆四情况组)
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231 (封印不可逆四情况组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:231 (封印不可逆四情况组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:231 (封印不可逆四情况组)
print("--- 封印不可逆四情况(禁撤封印 + 挖封印格兑付改判)---")
# 场景 A:安全格直挖(情况④)+ seal_pull/重复封印拒绝面
var rng_a = load("res://core/rng_service.gd").new()
@@ -687,8 +689,8 @@ func _smoke_seal_irreversible_quadrants() -> void:
# 负向字典逐型直验(类型化兑付行协议):找真实板上各型非封锁格,直调 grant_routed 负向路由(att 归因束形态),
# 断言每型的兑付形态(enrage/off_guard/sapped 效果行、heal 直伤行、blast 警觉行、action 行动点排队)。
func _smoke_negative_dictionary() -> void:
# oracle: design/gdd/systems/排雷循环/行动经济.md:256 (下轮行动点修正排队与兑现组)
# oracle: design/gdd/systems/排雷循环/行动经济.md:256 (下轮行动点修正排队与兑现组)
# 判据出处: design/gdd/systems/排雷循环/行动经济.md:256 (下轮行动点修正排队与兑现组)
# 判据出处: design/gdd/systems/排雷循环/行动经济.md:256 (下轮行动点修正排队与兑现组)
print("--- 负向字典逐型直验(sapped 在位 / heal 直伤 / blast 警觉 / action 行动点)---")
# 数据面:sapped 行在位(状态表七行 + effect_pool_map 映射在位)
var sapped: Dictionary = _db.combat.status_effects.row_by_id("sapped")
@@ -782,8 +784,8 @@ func _smoke_negative_dictionary() -> void:
# 资源面断言:chain/dig 事件恒携带 resource=true(危险格同权);initial 恒 false(封锁格);
# 封锁格查询 = null、危险格未开恒可查。
func _smoke_resource_surface() -> void:
# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235 (资源面只读查询组)
# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235 (资源面只读查询组)
# 判据出处: design/gdd/systems/战斗循环/地图战斗资源.md:235 (资源面只读查询组)
# 判据出处: design/gdd/systems/战斗循环/地图战斗资源.md:235 (资源面只读查询组)
print("--- 格面资源资格面(危险格携带 / 封锁格无资源)---")
var run1 = _run_battle(1, 0)
var run2 = _run_battle(2, 0)
@@ -827,3 +829,44 @@ func _smoke_resource_surface() -> void:
# 五段全序断言(战斗结算 验收"五段全序"+ 行型封闭 10 类):逐轮行相位序 = 授予 → 增益/减益 → 战斗 → 衰减 → 判定。
# ---- G8 reward 行读值改断(迭代 55「QA覆盖余量收口批」T8/V12——生产行驱动兑付链,替代内联合成行主证)----
# 现役断言(上方 action 型负向段)为内联合成行——行值变更零失败;本组读生产 reward 行对象走同一兑付链,
# 断言真值依赖生产行值(行值变更即红)。合成行旧断言保留并存(零删除)。
func _check_enemy_alert_production_row() -> void:
# 判据出处: design/engineering/test-conventions.md:133 (真实入口断言——生产行值驱动)
var row: Dictionary = _db.run.reward.row_by_id("enemy_alert")
Kit.check("enemy_alert 生产行值:blast 负向 effect=alert power0 weight0(不入池警觉行)〔QA余 V12〕",
String(row.get("cell_type", "")) == "blast" and String(row.get("polarity", "")) == "negative"
and String(row.get("effect_id", "")) == "alert" and int(row.get("power", -1)) == 0
and int(row.get("weight", -1)) == 0)
var rg = load("res://core/rng_service.gd").new()
rg.set_master_seed(20261108)
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rg)
Kit.check("警觉生产行驱动:战斗启动", bool(settlement.begin_battle({"level_index": 1, "node_depth": 3, "hero_hp": int(_db.combat.hero.max_hp)})))
settlement.reward_grant().grant_routed(row, String(row.get("polarity", "negative")),
{"source_class": "mine_hit", "cell": Vector2i(0, 0), "ri": 1})
var alert_ok := false
for eff_a in settlement._status_effects.query_active_side("enemy"):
if String(eff_a.effect_id) == String(row.get("effect_id", "")):
alert_ok = true
Kit.check("警觉兑付随生产行:effect_id=alert 状态在敌侧池(行 effect_id 变更即红)〔QA余 V12〕", alert_ok)
func _check_ap_drain_production_row() -> void:
# 判据出处: design/engineering/test-conventions.md:133 (真实入口断言——生产行值驱动)
var row: Dictionary = _db.run.reward.row_by_id("ap_drain")
Kit.check("ap_drain 生产行值:action 负向 power=1 weight0〔QA余 V12〕",
String(row.get("cell_type", "")) == "action" and String(row.get("polarity", "")) == "negative"
and int(row.get("power", -1)) == 1 and int(row.get("weight", -1)) == 0)
var rg = load("res://core/rng_service.gd").new()
rg.set_master_seed(20261109)
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rg)
Kit.check("迟滞生产行驱动:战斗启动", bool(settlement.begin_battle({"level_index": 1, "node_depth": 3, "hero_hp": int(_db.combat.hero.max_hp)})))
var d0: int = settlement.action_economy.pending_ap_delta()
settlement.reward_grant().grant_routed(row, String(row.get("polarity", "negative")),
{"source_class": "mine_hit", "cell": Vector2i(0, 0), "ri": 1})
Kit.check("迟滞兑付随生产行:排队增量==-row.power==-1(行 power 变更即红)〔QA余 V12〕",
settlement.action_economy.pending_ap_delta() == d0 - int(row.get("power", 0)))
@@ -419,7 +419,7 @@ var _db
func _init() -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (结算接线同种子逐项 golden 对拍组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:362 (结算接线同种子逐项 golden 对拍组)
_db = ConfigDBScript.new()
var load_ok: bool = _db.load_tables("res://data/tables", true)
Kit.check("前置:表加载零 error", load_ok and _db.diagnostics_count("error") == 0)
@@ -13,7 +13,7 @@ const Kit := preload("res://tests/unit/test_kit.gd")
func _init() -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:293 Boss 半血解锁技宿主(装载组)
# 判据出处: design/gdd/systems/战斗循环/敌人系统.md:293 Boss 半血解锁技宿主(装载组)
var db = ConfigDBScript.new()
var load_ok: bool = db.load_tables("res://data/tables", true)
Kit.check("前置:表加载零 error", load_ok and db.diagnostics_count("error") == 0)
@@ -26,7 +26,7 @@ func _init() -> void:
# 门谓词:卓尔 (2,9) hp 130(×mult 1.0)→ venom_curb unlock 50 → 阈值 round(130×0.5)=65
func _boss(db, L: int, D: int, kind := "boss"):
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var ec = UnitContainerScript.new(db)
var ok: bool = ec.spawn({"level_index": L, "node_depth": D, "hero_hp": int(db.combat.hero.max_hp), "row_kind": kind})
Kit.check("前置:spawn (%d,%d) 受理" % [L, D], ok)
@@ -45,7 +45,7 @@ func _has_sid(arr: Array, sid: String) -> bool:
func _test_gate_predicate(db) -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:143 (门谓词:条件不满足不进可摇集合组)
# 判据出处: design/gdd/systems/战斗循环/敌人系统.md:143 (门谓词:条件不满足不进可摇集合组)
var ec = _boss(db, 2, 9)
var u: Dictionary = _boss_u(ec)
var mhp: int = int(u.max_hp)
@@ -74,7 +74,7 @@ func _test_gate_predicate(db) -> void:
# 闩不可逆:解锁后治疗抬回满血 → 复查零新解锁、可摇集合不回退
func _test_latch_irreversible(db) -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:143 (闩型条件首次为真整场保持组)
# 判据出处: design/gdd/systems/战斗循环/敌人系统.md:143 (闩型条件首次为真整场保持组)
var ec = _boss(db, 2, 9)
var u: Dictionary = _boss_u(ec)
ec.apply_damage(u, int(u.max_hp) / 2) # → 半血触发
@@ -86,7 +86,7 @@ func _test_latch_irreversible(db) -> void:
# 普通行无字段逐位零扰动:地精 (1,0) 池快照零 unlock 键、过滤恒空转、可摇=全池
func _test_plain_rows_zero_drift(db) -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:301 (普攻倍率 1 特例——零漂移组)
# 判据出处: design/gdd/systems/战斗循环/敌人系统.md:301 (普攻倍率 1 特例——零漂移组)
var ec = load("res://core/unit_container.gd").new(db)
Kit.check("零扰动前置:巨蜘蛛首遇带 spawn(池三行全无 trigger——快照键策略钉死载体)", ec.spawn({"level_index": 2, "node_depth": 1, "hero_hp": int(db.combat.hero.max_hp)}))
var u: Dictionary = ec.all_enemies()[0]
@@ -9,7 +9,7 @@ const Kit := preload("res://tests/unit/test_kit.gd")
func _init() -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:143 (触发条件机制 V1–V10 组)
# 判据出处: design/gdd/systems/战斗循环/敌人系统.md:143 (触发条件机制 V1–V10 组)
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
print("[trigger_cond_test] FAIL — 数值表加载失败")
@@ -15,7 +15,7 @@ var _db
func _init() -> void:
# oracle: design/gdd/systems/排雷循环/地形变异.md:151 变异布置宿主(装载组)
# 判据出处: design/gdd/systems/排雷循环/地形变异.md:151 变异布置宿主(装载组)
_db = ConfigDBScript.new()
# 表加载失败不中止(承 G2/G3/G4/G5 同款:表损坏的红落在具体断言上——
# 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评)
@@ -28,8 +28,9 @@ func _init() -> void:
# 套执行序沿源编排(基点 _run_core_smoke 调用序——本组套的相对序)
_smoke_variant_placement(1)
_smoke_variant_placement(2)
_smoke_global_variant()
_smoke_global_variant() # 审计 U1 修复恢复(T7 编辑回退分支误删调用——33 条在役断言复归执行)
_test_pack_min_from_table()
_test_l3_rockfall_minedust_rows()
quit(Kit.finish("variant_placement_test"))
@@ -54,7 +55,7 @@ class _CountVariantRng extends RngServiceCore:
# 本段经 begin_battle 走完整初始化序后断言:门控一致性 / 布置域 / 数量公式 / 连通片上限 / 确定性。
func _smoke_variant_placement(L: int) -> void:
# oracle: design/gdd/systems/排雷循环/地形变异.md:63 (战内布置段门控组)
# 判据出处: design/gdd/systems/排雷循环/地形变异.md:63 (战内布置段门控组)
print("--- 地形变异 布置段(L%d)---" % L)
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(42)
@@ -148,7 +149,7 @@ func _variant_dump(b) -> String:
func _smoke_global_variant() -> void:
# oracle: design/gdd/systems/排雷循环/地形变异.md:74 (全局替换制与回落组)
# 判据出处: design/gdd/systems/排雷循环/地形变异.md:74 (全局替换制与回落组)
print("--- 全局作用域变异(门控三维 + 全局池空兜底回落 + 资源面偏斜)---")
# ① 跨字段互锁负样本(resource_face ⟺ scope=global 三条 + effect_value 新域 [0,50])
var vt = load("res://data/schemas/variation_table.gd").new()
@@ -343,7 +344,7 @@ func _errs_has_cross_field(errs: Array) -> bool:
# 全称不可断)。可达判据=①静态(布置面零字面量下界)+②同种子改值产物互异(真实入口=begin_battle 布置段)。
func _test_pack_min_from_table() -> void:
# oracle: design/gdd/systems/排雷循环/地形变异.md:63 (连通片格数 [pack_min,pack_max] 读表组)
# 判据出处: design/gdd/systems/排雷循环/地形变异.md:63 (连通片格数 [pack_min,pack_max] 读表组)
# ① 静态:布置面零字面量下界(randi_range(2, 退役——下界读 pack_min 表头单点)
var src := FileAccess.get_file_as_string("res://core/board_installer.gd")
Kit.check("布置面零字面量下界(randi_range(2, 退役、下界读表单点在位)〔架构整改 V10·静态〕",
@@ -368,4 +369,82 @@ func _test_pack_min_from_table() -> void:
found_diff = true
break
vt.pack_min = saved_min
Kit.check("同种子 pack_min=3 vs 1 布置产物互异(下界进 randi_range——改值可观测)〔架构整改 V10〕", found_diff)
Kit.check("同种子 pack_min=3 vs 1 布置产物互异(下界进 randi_range——改值可观测)〔架构整改 V10〕", found_diff)
# ---- G7 L3 变异行(迭代 55「QA覆盖余量收口批」T7/V11——rockfall/mine_dust 第三关布置与效果通道)----
# 真实入口=变异布置段(begin_battle L3→deep_underdark 候选池→真实布置);rockfall 效果通道=
# 授予段地形批(on_open 兑现 flat_damage);mine_dust=信息型遮蔽解析数据面(board.variant_ids×表行 effect)。
func _test_l3_rockfall_minedust_rows() -> void:
# 判据出处: design/gdd/systems/排雷循环/地形变异.md:63 (L3 布置段门控与效果兑现组)
var pool := {}
for r in _db.combat.variation.variant_pool(3, "deep_underdark"):
pool[String(r.variant_id)] = r
Kit.check("L3 deep_underdark 候选池含 rockfall 行(flat_damage 3·on_open)〔QA余 V11〕",
pool.has("rockfall") and String((pool["rockfall"] as Dictionary).get("effect", "")) == "flat_damage"
and int((pool["rockfall"] as Dictionary).get("effect_value", -1)) == 3)
Kit.check("L3 deep_underdark 候选池含 mine_dust 行(hide_number)〔QA余 V11〕",
pool.has("mine_dust") and String((pool["mine_dust"] as Dictionary).get("effect", "")) == "hide_number")
var rock_seed := -1
var dust_seed := -1
var rock_settlement = null
var dust_settlement = null
for s in range(1, 201):
var rg = load("res://core/rng_service.gd").new()
rg.set_master_seed(s)
var st = load("res://core/battle_orchestrator.gd").new(_db, rg)
if not st.begin_battle({"level_index": 3, "node_depth": 0, "hero_hp": int(_db.combat.hero.max_hp)}):
continue
var b = st.minesweeper.current_board()
if b == null or String(b.board_type) != "deep_underdark":
continue
var ids := {}
for c in b.variants.keys():
for vid in (b.variants[c] as Array):
ids[String(vid)] = true
if ids.has("rockfall") and rock_seed < 0:
rock_seed = s
rock_settlement = st
elif ids.has("mine_dust") and dust_seed < 0:
dust_seed = s
dust_settlement = st
if rock_seed > 0 and dust_seed > 0:
break
Kit.check("rockfall 放置可达(L3 真实布置段 variants 命中——200 种子内)〔QA余 V11〕", rock_seed > 0)
Kit.check("mine_dust 放置可达(同链)〔QA余 V11〕", dust_seed > 0)
# rockfall 效果通道:挖开变异格 → 授予段地形批兑付 flat_damage 3(伤害行 display 落石·值 3)
if rock_settlement != null:
var rows: Array = []
rock_settlement.step_row.connect(func(r): rows.append(r))
var b2 = rock_settlement.minesweeper.current_board()
var target := Vector2i(-1, -1)
for c in b2.variants.keys():
if not b2.is_mine(c):
target = c
break
Kit.check("rockfall 变异格在板(可挖)", target != Vector2i(-1, -1))
if target != Vector2i(-1, -1):
rock_settlement.minesweeper.fx_reveal(target)
rock_settlement.minesweeper.settle_batch([{"action": "stop"}])
var hit_val := -1
var hit_name := ""
for r in rows:
if String(r.get("type", "")) == "damage" and String(r.get("display_name", "")).contains("落石"):
hit_val = int(r.get("value", -1))
hit_name = String(r.get("display_name", ""))
Kit.check("rockfall 效果通道命中:挖开即伤兑付行值==3(表行 effect_value 驱动)〔QA余 V11〕",
hit_val == 3 and hit_name != "")
# mine_dust 效果通道:变异格经遮蔽解析数据面命中 hide_number 行(信息型不进结算——呈现读取)
if dust_settlement != null:
var b3 = dust_settlement.minesweeper.current_board()
var masked := false
var unmasked_clean := true
for c in b3.variants.keys():
for vid in (b3.variants[c] as Array):
if String(_db.combat.variation.row_by_id(String(vid)).get("effect", "")) == "hide_number":
masked = true
for c in b3.safe_cells():
if (b3.variants.get(c, []) as Array).is_empty() and not (b3.variant_ids(c) as Array).is_empty():
unmasked_clean = false
Kit.check("mine_dust 效果通道:变异格 hide_number 行命中(遮蔽解析数据面真值)且无变异格零遮蔽〔QA余 V11〕",
masked and unmasked_clean)
@@ -21,7 +21,7 @@ func _init() -> void:
# ---- 类型闸断言(V7——project.godot warning 配置在位〔源码断言〕+审计器脚本在位;
# 审计器归零判定走 static_scan S9 闸机,此处兜配置面)----
func _smoke_type_gate() -> void:
# oracle: design/engineering/code-conventions.md:41 (封装边界——类型闸源码扫描组)
# 判据出处: design/engineering/code-conventions.md:41 (封装边界——类型闸源码扫描组)
var pj: String = FileAccess.get_file_as_string("res://project.godot")
Kit.check("V7 类型闸 warning 配置在位(untyped_declaration=1 显形)〔迭代27 T5〕",
pj.contains("gdscript/warnings/untyped_declaration=1"))
@@ -32,7 +32,7 @@ func _smoke_type_gate() -> void:
# ---- 卫生件 grep 闸(V8——死代码/字符串 Callable/影子账残留零命中;源码扫描面)----
func _smoke_hygiene_gates() -> void:
# oracle: design/engineering/code-conventions.md:54 (模态覆盖层层级与登记纪律——卫生闸组)
# 判据出处: design/engineering/code-conventions.md:54 (模态覆盖层层级与登记纪律——卫生闸组)
var callables := 0
for d in ["res://core", "res://presentation", "res://data"]:
var dir := DirAccess.open(d)
@@ -18,7 +18,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:_check/汇
func _init() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var db = ConfigDBScript.new()
var load_ok: bool = db.load_tables("res://data/tables", true)
Kit.check("前置:表加载零 error", load_ok and db.diagnostics_count("error") == 0)
@@ -41,8 +41,8 @@ func _init() -> void:
# ---- B1 兑付配方词表:五配方/三目标规则/双结账政策——常量非空且互异(封闭枚举负空间) ----
func _test_damage_controller_vocabularies() -> void:
# oracle: design/engineering/architecture/architecture.md:89 (事件/事实/效果类型/算子四词表封闭——契约词表组)
# oracle: design/engineering/architecture/architecture.md:89 (事件/事实/效果类型/算子四词表封闭——契约词表组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·基础层」effect_engine 行 (事件/事实/效果类型/算子四词表封闭——契约词表组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·基础层」effect_engine 行 (事件/事实/效果类型/算子四词表封闭——契约词表组)
var kinds: Array = [
DamageControllerScript.KIND_HERO_ATTACK, DamageControllerScript.KIND_ENEMY_SKILL,
DamageControllerScript.KIND_BOARD_TYPED, DamageControllerScript.KIND_BOARD_BLAST,
@@ -72,8 +72,8 @@ func _test_damage_controller_vocabularies() -> void:
# ---- D2↔B1:掷点用途标签 ∈ RngService 封闭词表(EC12——跨系统字面量契约的成员性断言) ----
func _test_rng_tag_closure(db) -> void:
# oracle: design/engineering/architecture/architecture.md:90 (流 scope 封闭词表组)
# oracle: design/engineering/architecture/architecture.md:90 (流 scope 封闭词表组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·基础层」RngService 行 (流 scope 封闭词表组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·基础层」RngService 行 (流 scope 封闭词表组)
var stream_label: String = String(db.combat.settlement.damage_roll.armor_roll_stream)
Kit.check("settlement.armor_roll_stream ∈ RngService 封闭标签词表(EC12)",
RngServiceScript.PURPOSE_TAGS.has(stream_label))
@@ -87,8 +87,8 @@ func _test_rng_tag_closure(db) -> void:
# ---- B1→D1 行协议:单据九栏缺省齐备 + 协议公共键形态 ----
func _test_ticket_nine_columns() -> void:
# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (单据九列协议组)
# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (单据九列协议组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:362 (单据九列协议组)
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:362 (单据九列协议组)
var ctrl = _bare_controller()
var t: Dictionary = ctrl.open_ticket(DamageControllerScript.KIND_BOARD_FLAT_HERO, "mine_hit", "契约探针",
{"channel": "棋盘兑付", "base_value": 1, "target_rule": DamageControllerScript.TARGET_HERO, "ri": 0})
@@ -118,8 +118,8 @@ func _bare_controller():
# ---- B4→B3:增益授予布尔受理口径(受理/拒绝二元面 + 投影一致性) ----
func _test_status_boolean_acceptance(db) -> void:
# oracle: design/gdd/systems/战斗循环/增益状态.md:250 (受理布尔面组)
# oracle: design/gdd/systems/战斗循环/增益状态.md:250 (受理布尔面组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:250 (受理布尔面组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:250 (受理布尔面组)
var effects = StatusContainerScript.new(db)
# EC1 域闭:notify_battle_start 前授予 = false(受理面封闭)
var any_row: Dictionary = {}
@@ -146,8 +146,8 @@ func _test_status_boolean_acceptance(db) -> void:
# 个体键直传字面量("e1"/"e2"/"e3"——拼写约定单点在 StatusContainer.owner_key_for,测试面模拟授予路径逐只授)
func _test_status_individual_isolation(db) -> void:
# oracle: design/gdd/systems/战斗循环/增益状态.md:251 (按个体归属组)
# oracle: design/gdd/systems/战斗循环/增益状态.md:251 (按个体归属组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:251 (按个体归属组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:251 (按个体归属组)
var effects = StatusContainerScript.new(db)
effects.notify_battle_start()
var src := {"source_class": "seal_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0}
@@ -169,8 +169,8 @@ func _test_status_individual_isolation(db) -> void:
func _test_status_grant_all_per_unit(db) -> void:
# oracle: design/gdd/systems/战斗循环/增益状态.md:252 (授予即生效组)
# oracle: design/gdd/systems/战斗循环/增益状态.md:252 (授予即生效组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:252 (授予即生效组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:252 (授予即生效组)
var effects = StatusContainerScript.new(db)
effects.notify_battle_start()
var src := {"source_class": "mine_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0}
@@ -197,8 +197,8 @@ func _test_status_grant_all_per_unit(db) -> void:
func _test_status_death_clears(db) -> void:
# oracle: design/gdd/systems/战斗循环/增益状态.md:250 (战斗结束清空组)
# oracle: design/gdd/systems/战斗循环/增益状态.md:250 (战斗结束清空组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:250 (战斗结束清空组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:250 (战斗结束清空组)
var effects = StatusContainerScript.new(db)
effects.notify_battle_start()
var src := {"source_class": "seal_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0}
@@ -212,8 +212,8 @@ func _test_status_death_clears(db) -> void:
func _test_status_merge_per_unit(db) -> void:
# oracle: design/gdd/systems/战斗循环/增益状态.md:253 (只登记不去重组)
# oracle: design/gdd/systems/战斗循环/增益状态.md:253 (只登记不去重组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:253 (只登记不去重组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:253 (只登记不去重组)
var effects = StatusContainerScript.new(db)
effects.notify_battle_start()
var src := {"source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "round_index": 0}
@@ -236,8 +236,8 @@ func _test_status_merge_per_unit(db) -> void:
# ---- B4 结算链契约(断言补齐批——①增益状态结算链):衰减逐值/战斗结束全清/满层拒绝/独立并存/首授快照 ----
func _test_status_decay_and_clear(db) -> void:
# oracle: design/gdd/systems/战斗循环/增益状态.md:21 (先兑付后衰减组)
# oracle: design/gdd/systems/战斗循环/增益状态.md:21 (先兑付后衰减组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:21 (先兑付后衰减组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:21 (先兑付后衰减组)
var effects = StatusContainerScript.new(db)
effects.notify_battle_start()
var src := {"source_class": "seal_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0}
@@ -258,8 +258,8 @@ func _test_status_decay_and_clear(db) -> void:
func _test_status_stack_full_reject(db) -> void:
# oracle: design/gdd/systems/战斗循环/增益状态.md:255 (满层拒绝原子且留痕组)
# oracle: design/gdd/systems/战斗循环/增益状态.md:255 (满层拒绝原子且留痕组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:255 (满层拒绝原子且留痕组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:255 (满层拒绝原子且留痕组)
var effects = StatusContainerScript.new(db)
effects.notify_battle_start()
var src := {"source_class": "seal_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0}
@@ -277,8 +277,8 @@ func _test_status_stack_full_reject(db) -> void:
func _test_status_independent_branch(db) -> void:
# oracle: design/gdd/systems/战斗循环/增益状态.md:257 (合成不设上下限组)
# oracle: design/gdd/systems/战斗循环/增益状态.md:257 (合成不设上下限组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:257 (合成不设上下限组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:257 (合成不设上下限组)
var effects = StatusContainerScript.new(db)
effects.notify_battle_start()
var probe := {
@@ -303,8 +303,8 @@ func _test_status_independent_branch(db) -> void:
func _test_status_snapshot_first_grant(db) -> void:
# oracle: design/gdd/systems/战斗循环/增益状态.md:258 (快照抗重载组)
# oracle: design/gdd/systems/战斗循环/增益状态.md:258 (快照抗重载组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:258 (快照抗重载组)
# 判据出处: design/gdd/systems/战斗循环/增益状态.md:258 (快照抗重载组)
var effects = StatusContainerScript.new(db)
effects.notify_battle_start()
var src_first := {"source_class": "seal_hit", "origin_cell": Vector2i(3, 4), "round_index": 1}
+28 -4
View File
@@ -17,7 +17,7 @@ var _db
func _init() -> void:
# oracle: design/gdd/systems/冒险循环/冒险结构.md:281 冒险结构与专长宿主(装载组)
# 判据出处: design/gdd/systems/冒险循环/冒险结构.md:281 冒险结构与专长宿主(装载组)
_db = ConfigDBScript.new()
# 表加载失败不中止(承 G2/G3/G4/G5 同款:表损坏的红落在具体断言上——
# 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评)
@@ -59,7 +59,7 @@ func _hits_kind(hits: Array, kind: String) -> Array:
# ② 冒险结构 API 闸(状态机受理面——重复开始拒绝 / 入口停点 / 放弃两态)
func _smoke_adventure_api() -> void:
# oracle: design/gdd/systems/冒险循环/冒险结构.md:283 (冒险 API 组:开始受理/序列/回调)
# 判据出处: design/gdd/systems/冒险循环/冒险结构.md:283 (冒险 API 组:开始受理/序列/回调)
print("--- 冒险结构 API 闸(重复开始拒绝 · 入口停点 · 放弃两态受理)---")
var adr = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(42))
adr.set_hero_override(900, 400) # 首战速胜档(放弃的非战斗受理要走到选路态)
@@ -122,7 +122,7 @@ func _api_mark_node(_node: Dictionary, _info: Dictionary, seq: Array) -> void:
# ③ 冒险验收断言(映射 冒险结构.md 验收标准——表覆盖/满配冒险成功/种子复现/大奖励/败北线/
# 重复拒绝/GP 跨关延续/加算区同账/恢复数学/放弃)
func _smoke_adventure() -> void:
# oracle: design/gdd/systems/冒险循环/冒险结构.md:284 (冒险生命周期组:推进/末关/倒下/重开/生命账)
# 判据出处: design/gdd/systems/冒险循环/冒险结构.md:284 (冒险生命周期组:推进/末关/倒下/重开/生命账)
print("--- 冒险结构验收(三关序列 · 大奖励 · 摘要五件事 · 账本封闭)---")
var autoplay = load("res://sim/level_autoplay.gd")
# ① 表与覆盖义务(序列关数 = 行集关数;节点源 = 参数档在固定种子下的生成图,
@@ -463,7 +463,7 @@ func _smoke_adventure() -> void:
# ---- 专长冒烟(feats 四级 · 案D 池接线 · V3 订阅面逐件三列)----
func _smoke_feats() -> void:
# oracle: design/gdd/systems/冒险循环/奇物.md:465 (奇物授予与凭证保底组)
# 判据出处: design/gdd/systems/冒险循环/奇物.md:465 (奇物授予与凭证保底组)
print("--- 专长重设计实装(feat_table 四级 · 专长带池接线 · 订阅面逐件)---")
# ① 现行表牌库构成:37 件全量入池——102 数值令牌+37 relic 令牌(四级 rarity 直映射)
Kit.check("feat_table 已加载(37 件=基础 27+迁入 2+完美保险 1+迭代 46 换图系七件——换代)", _db.crossrun.feats != null and (_db.crossrun.feats.rows as Array).size() == 37)
@@ -577,6 +577,7 @@ func _smoke_feats() -> void:
offered = true
break
Kit.check("A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)", offered)
_check_ticket_quota_exhausted(a_t) # A6-补 配额满分支(迭代 55 T3/V4——生产配额值驱动恒 false)
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 快照/计数器键/余库逐枚还原)按主会话裁决②改判跨系统闭环宿主——
@@ -915,3 +916,26 @@ func _fresh_rng(seed_val: int):
func _level_active_nodes(level_index: int) -> Array:
var gen = NodeMapGenerator.new(_fresh_rng(42))
return (gen.generate(_db.run.level_gen_params.row_by_level_index(level_index), 42) as Dictionary).get("nodes", [])
# A6-补 配额满分支(迭代 55「QA覆盖余量收口批」T3/V4):ticket_appeared 达生产配额后恒不再出现——
# 生产 feat_ticket_quota==2 读表驱动;保底同置满使 false 只能来自配额分支(先于概率掷点)。
# 真实入口链 = node_curio_candidates→draw_candidates("reward_node")→feat_ticket_consider。
func _check_ticket_quota_exhausted(a_t) -> void:
# 判据出处: design/engineering/test-conventions.md:133 (真实入口断言——配额满分支售满后恒 false)
Kit.check("生产配额值在役:feat_ticket_quota==2〔QA余 V4〕", int(a_t.db.run.adventure.feat_ticket_quota) == 2)
a_t.stop()._deck.ticket_appeared = int(a_t.db.run.adventure.feat_ticket_quota) # 配额满注入(A6 同款测试通道直填)
a_t.stop()._deck.ticket_pity = int(a_t.db.run.adventure.feat_ticket_pity_step) # 保底同置满——false 唯一来源=配额分支
var rg_q: RandomNumberGenerator = a_t.rng.open_stream("reward_pick")
Kit.check("配额满后 consider 恒 false(保底已满仍拒——分支先于概率掷点)〔QA余 V4〕", not a_t.deck().feat_ticket_consider(rg_q))
for i in 3:
var cands: Array = (a_t.stop().node_curio_candidates().get("candidates", []) as Array)
var has_ticket := false
for cd in cands:
if String((cd as Dictionary).get("kind", "")) == "feat_ticket":
has_ticket = true
Kit.check("配额满后第 %d 次停点候选零专长凭证(注入链零凭证)〔QA余 V4〕" % (i + 1), not has_ticket)
Kit.check("配额满分支零记账(ticket_appeared 停在配额值——不随展示递增)〔QA余 V4〕",
int(a_t.stop()._deck.ticket_appeared) == int(a_t.db.run.adventure.feat_ticket_quota))
a_t.stop()._deck.ticket_appeared = 0 # 夹具还原(后续段零污染)
a_t.stop()._deck.ticket_pity = 0
@@ -16,7 +16,7 @@ func _init() -> void:
func _smoke_zero_chain_rule() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:230 (0 格连锁传播闭包——模拟玩家算子组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:230 (0 格连锁传播闭包——模拟玩家算子组)
var db = load("res://core/config_db.gd").new()
var load_ok: bool = db.load_tables("res://data/tables", true) and db.diagnostics_count("error") == 0
Kit.check("前置:表加载零 error", load_ok)
@@ -55,7 +55,7 @@ func _init() -> void:
# ① 缺省两参(真编排器):同种子两跑整轮驱动逐位(确定性自证——动作序列快照经 cell_revealed 事件流)
func _smoke_drive_round_contract() -> void:
# oracle: design/engineering/architecture/architecture.md:144 (效果端口件:收事件交结算取回命中批——驱动循环契约组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·功能层」battle_orchestrator 行(域文件集含 EffectPort) (效果端口件:收事件交结算取回命中批——驱动循环契约组)
var db = load("res://core/config_db.gd").new()
var load_ok: bool = db.load_tables("res://data/tables", true) and db.diagnostics_count("error") == 0
Kit.check("前置:表加载零 error", load_ok)
@@ -81,7 +81,7 @@ func _smoke_drive_round_contract() -> void:
# ② provider 形态:策略回调产出 dig/seal_place/refresh 各一——提交与日志回调四元组在案
func _provider_shapes() -> void:
# oracle: design/engineering/architecture/architecture.md:144 (端口应答束形状组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·功能层」battle_orchestrator 行(域文件集含 EffectPort) (端口应答束形状组)
var st := _StubSettlement.new()
var script := [ {"kind": "dig", "cell": Vector2i(1, 1)}, {"kind": "seal", "cell": Vector2i(2, 2)}, {"kind": "refresh"}, {"kind": "stop"} ]
var logs: Array = []
@@ -102,7 +102,7 @@ func _provider_shapes() -> void:
# ③ max_rejects:策略恒返被拒动作——第 3 拒后停手出环(循环次数=3)
func _max_rejects() -> void:
# oracle: design/engineering/architecture/architecture.md:144 (拒绝通道组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·功能层」battle_orchestrator 行(域文件集含 EffectPort) (拒绝通道组)
var st := _StubSettlement.new()
st.minesweeper.accept = false
st.minesweeper.reject_cells = {Vector2i(9, 9): true}
@@ -122,7 +122,7 @@ func _max_rejects() -> void:
# ④ 合步 next:move 后相位变更即断弃 dig(桩 on_submit 钩子把 phase 推离 Active)
func _combined_step_next() -> void:
# oracle: design/engineering/architecture/architecture.md:144 (合并步进组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·功能层」battle_orchestrator 行(域文件集含 EffectPort) (合并步进组)
var st := _StubSettlement.new()
st.minesweeper.on_submit = func(action: String) -> void:
if action == "move":
@@ -18,7 +18,7 @@ var _rng
func _init() -> void:
# oracle: design/gdd/systems/排雷循环/棋盘生成.md:219 棋盘生成宿主(装载组)
# 判据出处: design/gdd/systems/排雷循环/棋盘生成.md:219 棋盘生成宿主(装载组)
_db = ConfigDBScript.new()
# 表加载失败不中止(承 G2 同款:表损坏的红落在具体断言上——
# 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评)
@@ -52,7 +52,7 @@ func _mk_stream(stream_name: String, scope: int) -> RandomNumberGenerator:
# 公式 N×cell ≤ 可用高−12 必须计入 (N−1)×2px 网格间距,否则网格实高溢出被 clip_contents 裁掉;
# 现公式 = 计入间距 + 垂直预留 24px〔上 12/下 12——底部不贴 D 区边框〕)----
func _smoke_layout_cell_formula() -> void:
# oracle: design/gdd/systems/排雷循环/棋盘生成.md:75 (出生区边长表单点——格边长公式组)
# 判据出处: design/gdd/systems/排雷循环/棋盘生成.md:75 (出生区边长表单点——格边长公式组)
print("--- 棋盘布局格边长公式(底部预留回归·四档板型)---")
var board_generator = load("res://core/board_generator.gd").new()
var board_view = load("res://presentation/views/board_view.gd").new()
@@ -82,7 +82,7 @@ func _smoke_layout_cell_formula() -> void:
# ---- 地图战斗资源·身份面单元冒烟(格面资源:roll 恰一次 / 雷安同池 / 幂等 / 清图批尾)----
func _smoke_reward_identity_unit() -> void:
# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233 (格面资源身份——三分出口与资源身份组)
# 判据出处: design/gdd/systems/战斗循环/地图战斗资源.md:233 (格面资源身份——三分出口与资源身份组)
print("--- 地图战斗资源·格面资源身份面 ---")
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(20260911)
@@ -144,7 +144,7 @@ class _BoardRowStub:
func _smoke_board_generator() -> void:
# oracle: design/gdd/systems/排雷循环/棋盘生成.md:221 (确定性复现+布雷纪律组)
# 判据出处: design/gdd/systems/排雷循环/棋盘生成.md:221 (确定性复现+布雷纪律组)
print("--- 棋盘生成 ---")
var board_generator = load("res://core/board_generator.gd").new()
for L in [1, 2]:
@@ -239,7 +239,7 @@ func _board_dump(b) -> String:
# ---- 地图战斗资源·伤害量值区间冒烟(roll 域内 + 中点锚 + 变异实际流动 + 定值行不受扰)----
func _smoke_power_interval() -> void:
# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233 (资源量值区间——伤害量值区间组)
# 判据出处: design/gdd/systems/战斗循环/地图战斗资源.md:233 (资源量值区间——伤害量值区间组)
print("--- 地图战斗资源·伤害量值区间(上阶/下阶随机)---")
# ① 表侧静态锚(配置推导——改表不断锚):凡带 power_min/power_max 的行 = 区间行,
# 校验配对齐全(lo ≤ hi)+ power = floor 中点(行动经济 条款①期望当量锚);其余行必须零区间字段。
+7 -7
View File
@@ -13,7 +13,7 @@ var _db: ConfigDb
func _init() -> void:
# oracle: design/gdd/systems/排雷循环/棋盘生成.md:219 棋盘生成(装载组)
# 判据出处: design/gdd/systems/排雷循环/棋盘生成.md:219 棋盘生成(装载组)
_db = load("res://core/config_db.gd").new() as ConfigDb
if not _db.load_tables("res://data/tables", false):
Kit.check("数值表加载", false)
@@ -40,7 +40,7 @@ func _generate(L: int, D: int, seed0: int) -> BoardData:
func _test_generate_shape_and_mines() -> void:
# oracle: design/gdd/systems/排雷循环/棋盘生成.md:224 (布雷纪律组)
# 判据出处: design/gdd/systems/排雷循环/棋盘生成.md:224 (布雷纪律组)
var b := _generate(1, 0, 71000)
if b == null:
Kit.check("生成返回棋盘", false)
@@ -61,7 +61,7 @@ func _test_generate_shape_and_mines() -> void:
# 目标雷数公式单点(d14):同参同值对拍原式 + 真实产图指标读数同源
func _test_target_mine_count_single_source() -> void:
# oracle: design/gdd/systems/排雷循环/棋盘生成.md:224 (雷数单源组)
# 判据出处: design/gdd/systems/排雷循环/棋盘生成.md:224 (雷数单源组)
var bad := 0
var checked := 0
for L in [1, 2, 3]:
@@ -92,7 +92,7 @@ func _test_target_mine_count_single_source() -> void:
func _test_digits_equal_neighbor_mine_count() -> void:
# oracle: design/gdd/systems/排雷循环/棋盘生成.md:223 (数字恒真组)
# 判据出处: design/gdd/systems/排雷循环/棋盘生成.md:223 (数字恒真组)
var b := _generate(1, 1, 72000)
if b == null:
Kit.check("生成返回棋盘(数字段)", false)
@@ -113,7 +113,7 @@ func _test_digits_equal_neighbor_mine_count() -> void:
func _test_same_seed_bit_identical() -> void:
# oracle: design/gdd/systems/排雷循环/棋盘生成.md:221 (同种子逐位复现组)
# 判据出处: design/gdd/systems/排雷循环/棋盘生成.md:221 (同种子逐位复现组)
var a := _generate(2, 0, 73000)
var b := _generate(2, 0, 73000)
if a == null or b == null:
@@ -124,7 +124,7 @@ func _test_same_seed_bit_identical() -> void:
func _test_clearable_and_inferability_selfconsistent() -> void:
# oracle: design/gdd/systems/排雷循环/棋盘生成.md:226 (可清图与及格线自洽组)
# 判据出处: design/gdd/systems/排雷循环/棋盘生成.md:226 (可清图与及格线自洽组)
var b := _generate(1, 0, 74000)
if b == null:
Kit.check("生成返回棋盘(可推理段)", false)
@@ -140,7 +140,7 @@ func _test_clearable_and_inferability_selfconsistent() -> void:
# ---- V8 出生区边长表单点(架构整改——spawn_edge 表头驱动布雷域基/几何/护栏全导出)----
func _test_spawn_edge_from_table() -> void:
# oracle: design/gdd/systems/排雷循环/棋盘生成.md:75 (出生区边长读表组)
# 判据出处: design/gdd/systems/排雷循环/棋盘生成.md:75 (出生区边长读表组)
# ① 纯函数对拍:edge=5 夹具 → target = round(d×(W×H−25))(25 格布雷域基可观测)
var p5 := {"w": 12, "h": 12, "density": 0.15, "spawn_edge": 5}
Kit.check("V8 纯函数:edge=5 布雷域基 25 格(target=round(d×(144−25)))〔架构整改 V8〕",
@@ -15,7 +15,7 @@ var _bp_path := "user://test_breakpoint_resume.json"
func _init() -> void:
# oracle: design/gdd/systems/表现与支撑/存档序列化.md:180 (断点域契约:七字段往返/续玩确定性/幂等/三结局清除组)
# 判据出处: design/gdd/systems/表现与支撑/存档序列化.md:180 (断点域契约:七字段往返/续玩确定性/幂等/三结局清除组)
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
print("[breakpoint_resume_test] FAIL — 数值表加载失败")
@@ -13,7 +13,7 @@ const StatusTableScript := preload("res://data/schemas/status_effect_table.gd")
func _init() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:195 (跨表校验宿主——装载组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:195 (跨表校验宿主——装载组)
_test_all_links_valid()
_test_dangling_skill_id()
_test_cast_side_mismatch()
@@ -47,7 +47,7 @@ func _cats(errs: Array) -> Array[String]:
# ---- 正向:技能池全对齿(普攻恰一行、buff 挂敌方侧)应零错,防校验误伤 ----
func _test_all_links_valid() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:195 (悬空引用类别:跨表链接全有效组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:195 (悬空引用类别:跨表链接全有效组)
var skills := _skill([
{"skill_id": "attack", "type": "basic"},
{"skill_id": "raise_morale", "type": "buff", "buff_ref": "st_strong"},
@@ -63,7 +63,7 @@ func _test_all_links_valid() -> void:
# ---- 负向:技能池引用表里不存在的 skill_id 必须报 ----
func _test_dangling_skill_id() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:195 (悬空引用类别:skill_id 悬空组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:195 (悬空引用类别:skill_id 悬空组)
var skills := _skill([{"skill_id": "attack", "type": "basic"}])
var enemies := _enemy([
{"level_index": 1, "row_kind": "battle", "depth_band": 0, "enemy_id": "goblin", "pool": [
@@ -76,7 +76,7 @@ func _test_dangling_skill_id() -> void:
# ---- 负向:buff 挂到主角侧状态(side 反向)必须报 ----
func _test_cast_side_mismatch() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:195 (side 反向校验组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:195 (side 反向校验组)
var skills := _skill([{"skill_id": "raise_morale", "type": "buff", "buff_ref": "st_hero_side"}])
var enemies := _enemy([
{"level_index": 1, "row_kind": "battle", "depth_band": 0, "enemy_id": "goblin", "pool": [
@@ -12,7 +12,7 @@ var _resolver: ConfigDbResolver
func _init() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:202 (解析器宿主——装载组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:202 (解析器宿主——装载组)
_resolver = ResolverScript.new(
func(level, category, message) -> void: # 诊断通道签名按调用实参收(首参为表 id 字符串)
_diags.append("%s/%s/%s" % [str(level), str(category), str(message)]),
@@ -55,7 +55,7 @@ func _difficulty_table():
func _test_band_hit() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:202 (精确命中零警告组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:202 (精确命中零警告组)
var hit: Array = _resolver.resolve_enemy_rowset(1, 4, "battle")
Kit.check("带命中取偏难一侧:请求深度 4 命中带 5(逐字不回带 0)〔fv1〕",
hit.size() == 1 and String((hit[0] as Dictionary).get("enemy_id", "")) == "giant_spider")
@@ -65,7 +65,7 @@ func _test_band_hit() -> void:
func _test_rowset_order_preserved() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:202 (行集寻址与行序保序组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:202 (行集寻址与行序保序组)
var rows: Array = _resolver.resolve_enemy_rowset(1, 0, "battle")
var ids: Array[String] = []
for r in rows:
@@ -75,7 +75,7 @@ func _test_rowset_order_preserved() -> void:
func _test_top_band_clamp() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:202 (超最大行走封顶取最大行组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:202 (超最大行走封顶取最大行组)
_diags.clear()
var rows: Array = _resolver.resolve_enemy_rowset(1, 99, "battle")
Kit.check("越顶带 clamp 取顶带行集(battle 顶带 5)〔fv3〕",
@@ -91,7 +91,7 @@ func _test_top_band_clamp() -> void:
func _test_row_kind_routing() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:202 (行类别路由组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:202 (行类别路由组)
var battle_rows: Array = _resolver.resolve_enemy_rowset(1, 0, "battle")
var boss_rows: Array = _resolver.resolve_enemy_rowset(1, 0, "boss")
Kit.check("行类路由:同请求深度 battle 与 boss 命中不同键域〔fv4〕",
@@ -100,7 +100,7 @@ func _test_row_kind_routing() -> void:
func _test_request_oob() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:203 (越域致命中止组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:203 (越域致命中止组)
_diags.clear()
_resolver.resolve_enemy_rowset(0, 0, "battle")
Kit.check("关卡序号 <1 → fatal request_oob〔fv5〕", _has_fatal_oob())
@@ -120,7 +120,7 @@ func _has_fatal_oob() -> bool:
func _test_difficulty_clamp() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:202 (难度 clamp 组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:202 (难度 clamp 组)
var m: Dictionary = _resolver.resolve_difficulty_modifiers(1, 0)
Kit.check("难度乘数越域被 clamp:hp 99 → 上限 10", absf(float(m.get("hp_mult", 0.0)) - 10.0) < 0.001)
Kit.check("难度乘数越域被 clamp:atk 0.01 → 下限 0.1", absf(float(m.get("atk_mult", 0.0)) - 0.1) < 0.001)
@@ -8,7 +8,7 @@ const Kit := preload("res://tests/unit/test_kit.gd")
func _init() -> void:
# oracle: design/engineering/architecture/architecture.md:107 (CurioDeck:三选一/商店共享池、抽签、cap 库存——刷新付费组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·核心层」CurioDeck 行 (CurioDeck:三选一/商店共享池、抽签、cap 库存——刷新付费组)
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
print("[curio_refresh_paid_test] FAIL — 数值表加载失败")
+1 -1
View File
@@ -11,7 +11,7 @@ const EffectEngineScript = preload("res://core/effect_engine.gd")
func _init() -> void:
# oracle: design/engineering/architecture/architecture.md:89 (效果词表封闭——扩签封闭性组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·基础层」effect_engine 行 (效果词表封闭——扩签封闭性组)
# ① 词表在册断言(扩签面:2 事件+8 事实+7 效果类型+1 算子)
Kit.check_quiet(EffectEngineScript.EVENTS.has("dig_stop") and EffectEngineScript.EVENTS.has("battle_won"),
"新事件在册:dig_stop/battle_won〔专长批 V1〕")
@@ -11,34 +11,34 @@ const Kit := preload("res://tests/unit/test_kit.gd")
func _init() -> void:
# oracle: design/engineering/code-conventions.md:41 (封装边界:跨对象往来只走公开方法与属性——P2 词表单点扫描组)
# 判据出处: design/engineering/code-conventions.md:41 (封装边界:跨对象往来只走公开方法与属性——P2 词表单点扫描组)
_sweep_generator()
_sweep_production_face()
quit(Kit.finish("encapsulation_p2_sweep_test"))
func _src(path: String) -> String:
# oracle: design/engineering/code-conventions.md:41 (源码采样组)
# 判据出处: design/engineering/code-conventions.md:41 (源码采样组)
var src := FileAccess.get_file_as_string(path)
Kit.check("扫描面非空前置:%s" % path, src.length() > 0)
return src
func _zero(pattern: String, src: String, label: String) -> void:
# oracle: design/engineering/code-conventions.md:41 (零命中扫描组)
# 判据出处: design/engineering/code-conventions.md:41 (零命中扫描组)
var re := RegEx.create_from_string(pattern)
Kit.check("%s:%s 零命中〔架构P2〕" % [label, pattern], re.search(src) == null)
func _anchor(pattern: String, src: String, label: String) -> void:
# oracle: design/engineering/code-conventions.md:41 (正锚存在组)
# 判据出处: design/engineering/code-conventions.md:41 (正锚存在组)
var re := RegEx.create_from_string(pattern)
Kit.check("%s:%s 正锚在案〔架构P2〕" % [label, pattern], re.search(src) != null)
# ① V1 生成器词表定义删除与改指
func _sweep_generator() -> void:
# oracle: design/engineering/code-conventions.md:41 (生成器本地副本归零组)
# 判据出处: design/engineering/code-conventions.md:41 (生成器本地副本归零组)
var src := _src("res://core/node_map_generator.gd")
if src.length() == 0:
return
@@ -48,7 +48,7 @@ func _sweep_generator() -> void:
# ② V2 生产三目录定义唯一性(递归扫 .gd,恰好 1 处且落 level_table.gd)
func _sweep_production_face() -> void:
# oracle: design/engineering/code-conventions.md:41 (schema 唯一定点组)
# 判据出处: design/engineering/code-conventions.md:41 (schema 唯一定点组)
var files: Array[String] = []
for dir_path in ["res://core", "res://data", "res://presentation"]:
_collect_gd(dir_path, files)
@@ -14,7 +14,7 @@ const Kit := preload("res://tests/unit/test_kit.gd")
func _init() -> void:
# oracle: design/engineering/code-conventions.md:41 (封装边界——五处结构性零命中扫描组)
# 判据出处: design/engineering/code-conventions.md:41 (封装边界——五处结构性零命中扫描组)
_sweep_settlement_segments()
_sweep_orchestrator_methods()
_sweep_breakpoint_pair()
@@ -26,7 +26,7 @@ func _init() -> void:
# 源文件读取+非空前置(返 "" 即已判红——调用侧跳过后续正则断言)
func _src(path: String) -> String:
# oracle: design/engineering/code-conventions.md:41 (源码采样组)
# 判据出处: design/engineering/code-conventions.md:41 (源码采样组)
var src := FileAccess.get_file_as_string(path)
Kit.check("扫描面非空前置:%s" % path, src.length() > 0)
return src
@@ -34,21 +34,21 @@ func _src(path: String) -> String:
# 正则零命中断言(-pattern 意图写入断言文案——可 grep 定位)
func _zero(pattern: String, src: String, label: String, path: String) -> void:
# oracle: design/engineering/code-conventions.md:41 (零命中扫描组)
# 判据出处: design/engineering/code-conventions.md:41 (零命中扫描组)
var re := RegEx.create_from_string(pattern)
Kit.check("%s:%s 零命中〔封装5.1〕" % [label, pattern], re.search(src) == null)
# 正锚在案断言(正则命中 ≥1——新公开面/新形态在用)
func _anchor(pattern: String, src: String, label: String, path: String) -> void:
# oracle: design/engineering/code-conventions.md:41 (正锚存在组)
# 判据出处: design/engineering/code-conventions.md:41 (正锚存在组)
var re := RegEx.create_from_string(pattern)
Kit.check("%s:%s 正锚在案〔封装5.1〕" % [label, pattern], re.search(src) != null)
# ① V2 settlement 七文件(束注入换代——对编排器引用归零)
func _sweep_settlement_segments() -> void:
# oracle: design/engineering/code-conventions.md:41 (结算段封装扫描组)
# 判据出处: design/engineering/code-conventions.md:41 (结算段封装扫描组)
var classes := {
"res://core/settlement/grant_segment.gd": "GrantSegment",
"res://core/settlement/combat_segment.gd": "CombatSegment",
@@ -68,7 +68,7 @@ func _sweep_settlement_segments() -> void:
# ② V3 编排器搬家五方法零残留
func _sweep_orchestrator_methods() -> void:
# oracle: design/engineering/code-conventions.md:41 (编排器方法面扫描组)
# 判据出处: design/engineering/code-conventions.md:41 (编排器方法面扫描组)
var src := _src("res://core/battle_orchestrator.gd")
if src.length() == 0:
return
@@ -78,7 +78,7 @@ func _sweep_orchestrator_methods() -> void:
# ③ V4 断点域(adventure_runner → BreakpointStore 公开面)
func _sweep_breakpoint_pair() -> void:
# oracle: design/engineering/code-conventions.md:41 (断点对封装扫描组)
# 判据出处: design/engineering/code-conventions.md:41 (断点对封装扫描组)
var src := _src("res://core/adventure_runner.gd")
if src.length() == 0:
return
@@ -88,7 +88,7 @@ func _sweep_breakpoint_pair() -> void:
# ④ V5 存档双向公开面(save_manager↔slot_store)
func _sweep_save_pair() -> void:
# oracle: design/engineering/code-conventions.md:41 (存档对封装扫描组)
# 判据出处: design/engineering/code-conventions.md:41 (存档对封装扫描组)
var src_m := _src("res://core/save_manager.gd")
if src_m.length() > 0:
_zero("slots\\._", src_m, "save_manager 对槽位件私取归零", "res://core/save_manager.gd")
@@ -102,7 +102,7 @@ func _sweep_save_pair() -> void:
# ⑤ V6 效果宿主累积面(grant → BattleFxHost 公开累积方法)
func _sweep_fx_host_accumulators() -> void:
# oracle: design/engineering/code-conventions.md:41 (特效宿主累积面扫描组)
# 判据出处: design/engineering/code-conventions.md:41 (特效宿主累积面扫描组)
var src := _src("res://core/settlement/grant_segment.gd")
if src.length() == 0:
return
@@ -112,7 +112,7 @@ func _sweep_fx_host_accumulators() -> void:
# ⑥ V7 配置挂载面(config_db → 三 schema bind_resolver)
func _sweep_config_resolver() -> void:
# oracle: design/engineering/code-conventions.md:41 (配置挂载面扫描组)
# 判据出处: design/engineering/code-conventions.md:41 (配置挂载面扫描组)
var src := _src("res://core/config_db.gd")
if src.length() == 0:
return
+1 -1
View File
@@ -16,7 +16,7 @@ func EffectEngineKindProbe(_db) -> bool:
func _init() -> void:
# oracle: design/gdd/systems/冒险循环/奇物.md:465 (奇物凭证五件与留任二件组)
# 判据出处: design/gdd/systems/冒险循环/奇物.md:465 (奇物凭证五件与留任二件组)
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
print("[feat_vouchers_test] FAIL — 数值表加载失败")
+1 -1
View File
@@ -14,7 +14,7 @@ const LevelRunnerScript := preload("res://core/level_runner.gd")
func _init() -> void:
# oracle: design/gdd/systems/冒险循环/冒险结构.md:288 (生命一致性:三写入口封顶 [0,生效上限]——主角账组)
# 判据出处: design/gdd/systems/冒险循环/冒险结构.md:288 (生命一致性:三写入口封顶 [0,生效上限]——主角账组)
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
print("[hero_ledger_test] FAIL — 数值表加载失败")
@@ -10,7 +10,7 @@ const LevelTableScript := preload("res://data/schemas/level_table.gd")
func _init() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (信息迷雾类型可见性宿主——装载组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:176 (信息迷雾类型可见性宿主——装载组)
var db = load("res://core/config_db.gd").new()
var loaded: bool = db.load_tables("res://data/tables", false)
Kit.check_quiet(loaded, "可见性用例:数值表加载成功")
@@ -103,7 +103,7 @@ func _runner(db, nodes: Array, anchor_id: int, candidates: Array, n: int = 2):
# ---- ⑥ N 进数据表(含越域校验)〔fv6〕----
func _test_field_in_table(db) -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (迷雾参数读表——字段在表组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:176 (迷雾参数读表——字段在表组)
var n_in_table: int = int(db.run.level.info_visible_layers)
Kit.check("N 落表:level.tres 读数 2(实际 %d)〔fv6〕" % n_in_table, n_in_table == 2)
var t := LevelTableScript.new()
@@ -127,7 +127,7 @@ func _has_n_domain_err(errs: Array) -> bool:
# ---- ① 邻近 N 层见类型(层距边界)〔fv1〕----
func _test_layer_distance(db) -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (层距 N 可见性组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:176 (层距 N 可见性组)
var nodes := _graph_nodes()
var r = _runner(db, nodes, 1, [nodes[2]]) # 锚 = 节点 1(层 1),候选 = 节点 2
var vis: Array = r.type_visible_ids()
@@ -139,7 +139,7 @@ func _test_layer_distance(db) -> void:
# ---- ② 下一跳恒可见(跨层跳边候选)〔fv4〕----
func _test_candidate_exempt(db) -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (候选豁免组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:176 (候选豁免组)
var nodes := _graph_nodes()
var r = _runner(db, nodes, 6, [nodes[3]]) # 锚 = 节点 6(层 5),候选 = 节点 3(层 2,层距 3)
var vis: Array = r.type_visible_ids()
@@ -150,7 +150,7 @@ func _test_candidate_exempt(db) -> void:
# ---- ③ Boss/终点不入雾+⑤ 入口停点锚层 0〔fv3〕〔fv1〕----
func _test_boss_exempt(db) -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (Boss 豁免组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:176 (Boss 豁免组)
var nodes := _graph_nodes()
var r = _runner(db, nodes, -1, [nodes[0]]) # 入口停点(无当前节点,锚层 0)
var vis: Array = r.type_visible_ids()
@@ -163,7 +163,7 @@ func _test_boss_exempt(db) -> void:
# ---- ④ 单圈回雾(visited 不豁免)〔fv7〕----
func _test_single_circle(db) -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (单圈可见组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:176 (单圈可见组)
var nodes := _graph_nodes()
var r = _runner(db, nodes, 6, [nodes[7], nodes[3]])
r.visited.assign([nodes[0], nodes[1], nodes[2], nodes[3], nodes[4], nodes[5], nodes[6]])
@@ -176,7 +176,7 @@ func _test_single_circle(db) -> void:
# ---- ⑤ 单链退化形态(出度 ≤1 图,编号 = id 句柄)〔fv1〕----
func _test_single_chain_entry(db) -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (单链入口组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:176 (单链入口组)
var nodes := _chain_nodes()
var r = _runner(db, nodes, -1, [nodes[0]])
var vis: Array = r.type_visible_ids()
@@ -189,7 +189,7 @@ func _test_single_chain_entry(db) -> void:
# ---- ⑥ N 读表:可见窗随表值随动〔fv6〕----
func _test_n_from_table(db) -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (N 读表组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:176 (N 读表组)
var nodes := _graph_nodes()
var r = _runner(db, nodes, 1, [nodes[2]]) # 锚层 1
db.run.level.info_visible_layers = 1
@@ -207,7 +207,7 @@ func _test_n_from_table(db) -> void:
# ---- ⑦ 续玩重建同值〔fv7〕----
func _test_resume_rebuild(db) -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var t := LevelTableScript.new()
t.info_visible_layers = 2
t.rows = [{"level_index": 1, "display_name": "夹具关", "rest_heal_pct": 30}]
+14 -14
View File
@@ -232,7 +232,7 @@ func _board_row_exact(level_index: int, depth: int) -> bool:
func _init() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
_db = ConfigDBScript.new()
# 表加载失败不中止(承 G2/G3/G4/G5 同款:表损坏的红落在具体断言上——
# 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评)
@@ -262,7 +262,7 @@ func _init() -> void:
# ---- 数值入表等价证明(d11/d16a——原字面量迁移:表值 == 迁移前字面量,消费端读表)----
func _smoke_table_migration() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:195 (行集纪律——表迁移组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:195 (行集纪律——表迁移组)
print("--- 数值入表(shop 两 knob + hero 显示名 · 原值迁移)---")
Kit.check("shop.relic_base_price 表值 == 迁移前字面量(15.0)〔wd11〕", _db.run.shop.relic_base_price == 15.0)
Kit.check("hero.display_name 表值 == 迁移前字面量(探险者)〔wd16〕", _db.combat.hero.display_name == "探险者")
@@ -277,7 +277,7 @@ func _smoke_table_migration() -> void:
# 敌侧/板侧解析一律走 ConfigDB 真实消费链,禁直读行集自算)----
func _smoke_generated_coverage() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184 (每终点前恰一店——生成覆盖扫描组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:184 (每终点前恰一店——生成覆盖扫描组)
var enemy_ok := true
var board_ok := true
var boss_band_ok := true
@@ -328,7 +328,7 @@ func _smoke_generated_coverage() -> void:
# ---- 关卡节点图·生成图推进冒烟(图 = 该关参数档 × 种子)----
func _smoke_level() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (关卡图校验推进组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:178 (关卡图校验推进组)
print("--- 关卡节点图·生成图推进(参数档×种子产图·满配冒险成功/恢复公式/败北线/回放确定性)---")
var autoplay = load("res://sim/level_autoplay.gd")
var hero_base := {"atk": int(_db.combat.hero.atk), "hp": int(_db.combat.hero.max_hp), "ac": int(_db.combat.hero.armor_class)} # 配置基线快照——⑤ 回基线对比用(改表自动跟随,不再硬编码)
@@ -442,7 +442,7 @@ func _drive_to_rest(level_index: int, seed_val: int, gap: int) -> Dictionary:
# ---- 分支节点图冒烟:图合法性校验 + L2 幽暗地域生成图行进(图 = 参数档 × 种子)----
func _smoke_level_graph() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (图结构与行进纪律组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:178 (图结构与行进纪律组)
print("--- 关卡节点图·分支节点图(L2 幽暗地域·生成图)---")
var autoplay = load("res://sim/level_autoplay.gd")
# ① 关卡表行级校验零错误(关卡表只承载序号/显示名/恢复比例——图合法性由 validate_graph 在起关时承担)
@@ -801,7 +801,7 @@ func _fresh_rng(seed_val: int):
# ---- ① GP 冒险钱包·到账 ----
func _smoke_gp_wallet() -> void:
# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233 (GP 钱包入账组)
# 判据出处: design/gdd/systems/战斗循环/地图战斗资源.md:233 (GP 钱包入账组)
print("--- GP 冒险钱包·到账(战斗完成入账恰一次 + Boss 加值)---")
var autoplay = load("res://sim/level_autoplay.gd")
# ① L1 首战(普通战档——掷定 [2,4] + 关级加值 0):runner 驱动,收集 battle_finished 与钱包事件
@@ -867,7 +867,7 @@ func _escrow_sum_of(rows: Array) -> int:
# ---- ② escrow 隔离(GP安全格兑付 / 破财直验 / 钱包 clamp)----
func _smoke_gp_escrow() -> void:
# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233 (GP escrow 组)
# 判据出处: design/gdd/systems/战斗循环/地图战斗资源.md:233 (GP escrow 组)
print("--- GP escrow 隔离(GP安全格兑付入缓冲 · 破财不透支)---")
# ① 种子扫描:找GP 袋(gp 型安全格)可挖掘局——挖开兑付 +3 入 escrow,战斗末随 gp_dropped 一次上抛
var scan := {}
@@ -981,7 +981,7 @@ func _find_gp_dig(minesweeper, settlement) -> Dictionary:
# ---- ③ 商店房三收口 ----
func _smoke_shop_room() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184 (商店房呈现组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:184 (商店房呈现组)
print("--- 商店房三收口(现价/加算区/余额不足拒/离开回选路/收束清零)---")
var autoplay = load("res://sim/level_autoplay.gd")
# 行进到商店房(按图型别定位商店节点——候选取最小句柄行进,到达即停):
@@ -1115,7 +1115,7 @@ func _shop_products_by_id(prods: Array) -> Dictionary:
# ---- ④ 商店编排性质(生成图扫描面——三条性质 + 终点跨表解析)----
func _smoke_shop_graph() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184 (必经恰一店图纪律组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:184 (必经恰一店图纪律组)
print("--- 商店编排性质(每终点前一位恰一店 · 店数=终点数 · 全路径恰穿一店)---")
var pred_bad := 0
var term_total := 0
@@ -1186,7 +1186,7 @@ func _smoke_shop_graph() -> void:
# ---- ⑥ 大岔口分区域兑付(V3——多终点图走哪条路决定打谁,任一 Boss 胜即收束恰一次)----
func _smoke_multi_terminal_settlement() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:185 (多终局收束组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:185 (多终局收束组)
print("--- 大岔口分区域兑付(L2 两轴各打各的终点)---")
var autoplay = load("res://sim/level_autoplay.gd")
var nodes: Array = _generated_graph(2, 42).get("nodes", [])
@@ -1254,7 +1254,7 @@ func _paths_all_one_shop(nodes: Array) -> bool:
# ---- ⑤ 商店共享池(三选一同构/乘法价/跨渠道共账/切边/断点扩键)----
func _smoke_shop_pool() -> void:
# oracle: design/engineering/architecture/architecture.md:107 (商店共享池组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·核心层」CurioDeck 行 (商店共享池组)
print("--- 商店共享池(抽3买1全重抽 · 乘法定价 ceil · 池空硬闸 · 切边零入店)---")
var autoplay = load("res://sim/level_autoplay.gd")
var shelf_kinds: Array = []
@@ -1417,7 +1417,7 @@ func _smoke_shop_pool() -> void:
# ---- 商店房 node_entered 载荷(尾波 V1——_enter_node pool/legacy 双发射路径归一后的行为保持断言)----
func _test_shop_node_entered_payload() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184 (行进至此打开商店覆盖层——载荷组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:184 (行进至此打开商店覆盖层——载荷组)
print("--- 商店房 node_entered 载荷(pool 含 shelf:true+gp 现值/legacy 无 shelf 键)---")
var autoplay = load("res://sim/level_autoplay.gd")
# pool 态:载荷字典经信号闭包当场取样(字典为引用累积器——lambda 按值捕获标量的绕行惯例)
@@ -1576,7 +1576,7 @@ func _has_err_category(errs: Array, category: String) -> bool:
# ---- 货架位数读表(架构整改——shelf_size 表头单点:进店与重抽同源)----
func _test_shelf_size_from_table() -> void:
# oracle: design/ux/shop.md:13 (货架位数读表组)
# 判据出处: 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)
@@ -1598,7 +1598,7 @@ func _test_shelf_size_from_table() -> void:
# ---- 散折价收回规则键(架构整改 R2——shop.spend_discount 表字段退役,折价读 economy_spend.discount_pct)----
func _test_spend_discount_from_rule_key() -> void:
# oracle: design/gdd/systems/排雷循环/行动经济.md:256 (换图基座:挖格产 RAP 与刷新折扣组)
# 判据出处: design/gdd/systems/排雷循环/行动经济.md:256 (换图基座:挖格产 RAP 与刷新折扣组)
# 字段真删:shop.tres 加载零诊断 + spend_discount 属性不在位
Kit.check("shop.tres 加载零诊断(spend_discount 字段真删——schema 与表行同卸)〔架构整改 V2〕",
_db.diagnostics_count("error") == 0 and _db.run.shop.get("spend_discount") == null)
@@ -11,7 +11,7 @@ const LevelTableScript := preload("res://data/schemas/level_table.gd")
func _init() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (关卡表消费侧校验宿主)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:178 (关卡表消费侧校验宿主)
_test_positive()
_test_negative()
_test_scope_not_extended()
@@ -38,7 +38,7 @@ func _has(errs: Array, cat: String, needle: String = "") -> bool:
# ---- 正例(四检全过)----
func _test_positive() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (良构基底正向组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:178 (良构基底正向组)
# 多入口多终点:两入口共汇入一岔口,两终点各在互异深度
var multi := [
_n(0, "battle", 0, [2]), _n(1, "battle", 0, [2]),
@@ -61,7 +61,7 @@ func _test_positive() -> void:
# ---- 负例九类(各报对应类别与判据消息)----
func _test_negative() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (违规配置加载期致命——负向组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:178 (违规配置加载期致命——负向组)
var non_boss_end := [_n(0, "battle", 0, [1]), _n(1, "battle", 1, [])]
Kit.check("负例:终点非 Boss 命中「终点恒为 Boss」〔v1〕",
_has(_errs(non_boss_end), "structural", "终点 1 非 Boss"))
@@ -95,7 +95,7 @@ func _test_negative() -> void:
# ---- 「勿扩勿缩」反证:本检不含商店位与个数对参数档的核对 ----
func _test_scope_not_extended() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (校验范围不外扩组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点地图.md:178 (校验范围不外扩组)
# 合法形状但全程无商店/三个终点同入口图——四检不该因此报错(商店位归冒烟断言、个数归产出侧)
var no_shop := [_n(0, "battle", 0, [1, 2]), _n(1, "battle", 1, [3]), _n(2, "rest", 1, [3]), _n(3, "boss", 2, [])]
Kit.check("范围反证:无商店图不在加载校验内报错(商店位由冒烟断言把关)〔v1〕", _errs(no_shop).is_empty())
@@ -21,7 +21,7 @@ var _db
func _init() -> void:
# oracle: design/gdd/systems/跨局循环/元进度.md:148 元进度与锻造宿主(装载组)
# 判据出处: design/gdd/systems/跨局循环/元进度.md:148 元进度与锻造宿主(装载组)
_db = ConfigDBScript.new()
# 表加载失败不中止(承 G2/G3/G4/G5 同款:表损坏的红落在具体断言上——
# 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评)
@@ -42,6 +42,10 @@ func _init() -> void:
_test_blessing_no_forge_leak()
_test_default_axes_pre_key()
_test_armor_switch_dual_axes()
_check_ks_workshop_row()
_check_ks_refresh_row()
_check_fork_rows()
_check_fork_side_projection()
quit(Kit.finish("meta_progression_forge_test"))
@@ -56,7 +60,9 @@ const _META_SMOKE_PATHS := ["user://smoke_meta_buy.json", "user://smoke_meta_dry
"user://smoke_meta_career_v5.json", "user://smoke_meta_treasure_unlock.json", "user://smoke_meta_treasure_blank.json",
"user://smoke_meta_forge.json", "user://smoke_meta_forge2.json", "user://smoke_meta_forge3.json",
"user://smoke_meta_forge4.json", "user://smoke_meta_forge5.json",
"user://smoke_meta_reject.json", "user://smoke_meta_blessing.json", "user://smoke_meta_axesw.json"]
"user://smoke_meta_reject.json", "user://smoke_meta_blessing.json", "user://smoke_meta_axesw.json",
"user://smoke_meta_ks_workshop.json", "user://smoke_meta_ks_refresh.json",
"user://smoke_meta_fork_rows.json", "user://smoke_meta_fork_side.json"]
func _meta_smoke_cleanup() -> void:
@@ -133,7 +139,7 @@ func _auto_play_round(settlement, out: Dictionary) -> void:
# ---- 元进度 ②树购买面(购买受理/持久/余额不足拒绝/未知节点拒绝;
# 行价=表值 flat 单价)----
func _meta_pricing_and_buy() -> void:
# oracle: design/gdd/systems/跨局循环/天赋树.md:198 (单币可判:购买只扣钥石、余额不足拒绝零变化组)
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:198 (单币可判:购买只扣钥石、余额不足拒绝零变化组)
print("--- 元进度·树购买面(隔离档 smoke_meta_buy/dry)---")
_meta_smoke_cleanup()
var sv = _meta_new_save(_META_SMOKE_PATHS[0])
@@ -160,7 +166,7 @@ func _meta_pricing_and_buy() -> void:
# ---- 元进度 ⑥Boss 两档 ⑦钥石不丢 ⑧工坊解锁 ⑩摘要副本与 cap(合成摘要驱动——settle 纯账面零跑局)----
func _meta_settle_and_workshop() -> void:
# oracle: design/gdd/systems/跨局循环/元进度.md:150 (钥石收束入账不丢组)
# 判据出处: design/gdd/systems/跨局循环/元进度.md:150 (钥石收束入账不丢组)
print("--- 元进度·收束入账与工坊(隔离档 smoke_meta_settle/workshop/cap)---")
var sv = _meta_new_save(_META_SMOKE_PATHS[2])
var mp = load("res://core/meta_progression.gd").new(_db, sv)
@@ -353,7 +359,7 @@ func _meta_settle_and_workshop() -> void:
# ---- 元进度 ④注入可见 + 白板成立(验收 4/2/3——程序面;MC 逐位回归归 T7 全量验证)----
func _meta_injection() -> void:
# oracle: design/gdd/systems/跨局循环/天赋树.md:201 (注入可判:开局覆盖值分层可加组)
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:201 (注入可判:开局覆盖值分层可加组)
print("--- 元进度·注入可见与白板(隔离档 smoke_meta_white/dry)---")
var sv = _meta_new_save(_META_SMOKE_PATHS[1])
var mp = load("res://core/meta_progression.gd").new(_db, sv)
@@ -397,7 +403,7 @@ func _meta_injection() -> void:
# ---- 锻造工坊·两根类型轴所有权移交(V5 前半:hero.tres 两轴删除+锻造配置默认值)----
# 后半(build_adventure_params 注入默认值/选中覆盖)随 T4 注入接线同批断言;写口/解锁面归 T3。
func _smoke_forge_axes() -> void:
# oracle: design/gdd/systems/跨局循环/锻造工坊.md:220 (锻造两轴单选注入与树零攻防组)
# 判据出处: design/gdd/systems/跨局循环/锻造工坊.md:220 (锻造两轴单选注入与树零攻防组)
print("--- 锻造工坊·类型轴所有权移交(T2)---")
# V5:hero.tres 静态表两轴已删(全表加载零违例——config_db_test 同款闸;此处钉字段不在位)
Kit.check("hero.tres 无 attack_type/armor_type 字段(契约 2——静态表两轴删除)〔锻造工坊批 V5〕",
@@ -560,7 +566,7 @@ func _smoke_forge_axes() -> void:
# ---- 天赋树冒烟(v0.8 12 节点:schema/购买面/Fork 一节点双态/彩蛋特化)----
func _smoke_tree() -> void:
# oracle: design/gdd/systems/跨局循环/天赋树.md:194 (天赋树购买与六键合成组)
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:194 (天赋树购买与六键合成组)
print("--- 天赋树(12 节点 · 购买面 · Fork 双态 · 彩蛋觉醒)---")
# 〔V1〕结构:12 行 + schema 零违例
Kit.check("meta_tree 已加载(12 节点=脊柱 2〔生命/命中淬炼一线一节点 cap4〕+锻造 2〔钥匙+彩蛋——彩蛋含在锻造 2 内非第 13 行〕+钥石 2〔秘宝匣/刷新升级〕+同调 6〔socket 3+Fork 3 双态〕——v0.8 终态)〔天赋树批 V1〕",
@@ -643,7 +649,7 @@ func _smoke_tree() -> void:
# ---- 天赋树六键行为冒烟(V10:开局姿态窗/危险直觉限次/财富之道聚散/守侧失误补偿;
# 引擎直构 + 编排授予段直注(_grant_seg._judge_seal=结算授予段封印判定单口))----
func _smoke_tree_rules() -> void:
# oracle: design/gdd/systems/跨局循环/天赋树.md:194 (规则键参数读表组)
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:194 (规则键参数读表组)
print("--- 天赋树六键行为(V10:窗/限次/聚散/守侧)---")
# 开局姿态组:每关首轮窗(battle_index 事实——引擎直构 + 自建 ctx)
var fx = load("res://core/effect_engine.gd").new(null)
@@ -784,7 +790,7 @@ func _smoke_tree_rules() -> void:
# ---- 锻造契约补齐(断言补齐批——⑥锻造工坊):拒购零副作用/祝福件零泄漏/默认值解耦/甲型双轴 ----
func _test_forge_buy_reject() -> void:
# oracle: design/gdd/systems/跨局循环/锻造工坊.md:222 (钥石不足购买被拒、余额与等级零变化组)
# 判据出处: design/gdd/systems/跨局循环/锻造工坊.md:222 (钥石不足购买被拒、余额与等级零变化组)
var sv = _meta_new_save("user://smoke_meta_reject.json")
var mp = load("res://core/meta_progression.gd").new(_db, sv)
sv.meta_state._add_keystone(10) # 低于首购价 20——余额不足面
@@ -799,7 +805,7 @@ func _test_forge_buy_reject() -> void:
func _test_blessing_no_forge_leak() -> void:
# oracle: design/gdd/systems/跨局循环/锻造工坊.md:220 (树淬炼对攻击与 AC 零贡献组)
# 判据出处: design/gdd/systems/跨局循环/锻造工坊.md:220 (树淬炼对攻击与 AC 零贡献组)
var sv = _meta_new_save("user://smoke_meta_blessing.json")
var mp = load("res://core/meta_progression.gd").new(_db, sv)
var fs0: Dictionary = sv.meta_state.forge_state().duplicate(true)
@@ -818,7 +824,7 @@ func _test_blessing_no_forge_leak() -> void:
func _test_default_axes_pre_key() -> void:
# oracle: design/gdd/systems/跨局循环/锻造工坊.md:217 (未点亮期间默认值照常注入组)
# 判据出处: design/gdd/systems/跨局循环/锻造工坊.md:217 (未点亮期间默认值照常注入组)
var sv = _meta_new_save("user://smoke_meta_axesw.json")
var mp = load("res://core/meta_progression.gd").new(_db, sv)
var ax: Dictionary = mp.hero_type_axes()
@@ -827,7 +833,7 @@ func _test_default_axes_pre_key() -> void:
func _test_armor_switch_dual_axes() -> void:
# oracle: design/gdd/systems/跨局循环/锻造工坊.md:221 (换装不丢级不生效——双轴组)
# 判据出处: design/gdd/systems/跨局循环/锻造工坊.md:221 (换装不丢级不生效——双轴组)
var sv = _meta_new_save("user://smoke_meta_axesw.json")
sv.meta_state._add_keystone(1000)
var mp = load("res://core/meta_progression.gd").new(_db, sv)
@@ -852,3 +858,115 @@ func _test_armor_switch_dual_axes() -> void:
return
Kit.check("甲型双轴:切回 light 复原(AC 10+0)且 heavy 等级保留(Lv1 不丢)〔断言补齐批 V16〕",
int(adr2.runner().effective_ac()) == 10 and sv.meta_state.forge_state().levels.get("heavy", 0) == 1)
# ---- G5 天赋树行与投影断言(迭代 55「QA覆盖余量收口批」T5——ks_workshop/ks_refresh/fork 两行/侧投影+spine 读行)----
func _check_ks_workshop_row() -> void:
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:30 (钥匙门行——前置/零价/柜台展开)
var row: Dictionary = _db.crossrun.meta_tree.row_by_id("ks_workshop")
Kit.check("ks_workshop 行值:keystone 脉络 unlock 行且前置=[sp_hp](脊柱生命第 1 级)〔QA余 V7〕",
String(row.get("vein", "")) == "keystone" and String(row.get("kind", "")) == "unlock"
and (row.get("requires", []) as Array) == ["sp_hp"])
Kit.check("ks_workshop 零价行值:currency=free 且 price=0(初始零购零币可点亮族)〔QA余 V7〕",
String(row.get("currency", "")) == "free" and int(row.get("price", -1)) == 0)
var sv = _meta_new_save("user://smoke_meta_ks_workshop.json")
var mp = load("res://core/meta_progression.gd").new(_db, sv)
sv.meta_state._add_keystone(1000)
Kit.check("前置未满足不可购(sp_hp 未点亮——钥匙门锁定)", not mp.tree_buy("ks_workshop"))
Kit.check("脊柱生命 L1 购买受理(前置行)", mp.tree_buy("sp_hp"))
Kit.check("购买前投影 level=0(锁定态行在投影)", _ks_row_level(mp, "ks_workshop") == 0)
var bal_pre: int = sv.meta_state.keystone_balance()
Kit.check("ks_workshop 购买受理(零价 free)", mp.tree_buy("ks_workshop"))
Kit.check("购买后投影 level=1(钥匙门开——柜台展开判据)且零币(余额差分零)",
_ks_row_level(mp, "ks_workshop") == 1 and int(sv.meta_state.keystone_balance()) == bal_pre)
_meta_smoke_cleanup()
# 树面投影行 level 读数(_check_ks_workshop_row/_check_ks_refresh_row 共用读数面)
func _ks_row_level(mp, node_id: String) -> int:
for r in (mp.tree_camp_status()["rows"] as Array):
if String((r as Dictionary).get("node_id", "")) == node_id:
return int((r as Dictionary).get("level", 0))
return -1
func _check_ks_refresh_row() -> void:
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:30 (刷新升级行——同账路由与零刷新分支)
var row: Dictionary = _db.crossrun.meta_tree.row_by_id("ks_refresh")
Kit.check("ks_refresh 行值:rule 行 keystone 币 price=30 cap=2 前置=[ks_workshop]〔QA余 V7〕",
String(row.get("kind", "")) == "rule" and String(row.get("currency", "")) == "keystone"
and int(row.get("price", -1)) == 30 and int(row.get("cap", -1)) == 2
and (row.get("requires", []) as Array) == ["ks_workshop"])
var sv = _meta_new_save("user://smoke_meta_ks_refresh.json")
var mp = load("res://core/meta_progression.gd").new(_db, sv)
sv.meta_state._add_keystone(1000)
var bal0: int = sv.meta_state.keystone_balance()
Kit.check("ks_refresh 购买受理(tree_buy 同账路由→buy_attune_refresh)", mp.tree_buy("ks_refresh"))
Kit.check("同账写读:refresh_level=1(树上+秘宝匣装配段双入口同一账本)", int(sv.meta_state.attune_state().refresh_level) == 1)
Kit.check("同账币值:扣 meta 行 L1 keystone_cost=30(非树行 price 面定夺)", int(sv.meta_state.keystone_balance()) == bal0 - 30)
Kit.check("树节点账零写入(同账语义——nodes 无 ks_refresh)", int(sv.meta_state.tree_state().nodes.get("ks_refresh", 0)) == 0)
Kit.check("第二次购买受理(L2 keystone_cost=60)", mp.tree_buy("ks_refresh") and int(sv.meta_state.attune_state().refresh_level) == 2)
var bal2: int = sv.meta_state.keystone_balance()
Kit.check("零刷新分支:L2 后再购被拒(attune_refresh_row(3) 空返——升级行族耗尽)〔QA余 V7〕", not mp.tree_buy("ks_refresh"))
Kit.check("零刷新分支零消费零升账", int(sv.meta_state.attune_state().refresh_level) == 2 and int(sv.meta_state.keystone_balance()) == bal2)
Kit.check("同账投影:树面 ks_refresh 行 level 读 Y 账本(=2)", _ks_row_level(mp, "ks_refresh") == 2)
_meta_smoke_cleanup()
func _check_fork_rows() -> void:
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:30 (fork 一节点双态行——行值经购买面驱动)
for spec in [{"id": "fork_sense", "group": "fg_sense", "na": "锋", "nb": "守"},
{"id": "fork_economy", "group": "fg_economy", "na": "聚", "nb": "散"}]:
var row: Dictionary = _db.crossrun.meta_tree.row_by_id(String(spec["id"]))
Kit.check("%s 行值:fork 行 price=25 前置=[att_socket1] 组=%s〔QA余 V7〕" % [spec["id"], spec["group"]],
String(row.get("kind", "")) == "fork" and int(row.get("price", -1)) == 25
and (row.get("requires", []) as Array) == ["att_socket1"]
and String(row.get("fork_group", "")) == String(spec["group"]))
Kit.check("%s 侧名行值:name_a=%s / name_b=%s〔QA余 V7〕" % [spec["id"], spec["na"], spec["nb"]],
String(row.get("name_a", "")) == String(spec["na"]) and String(row.get("name_b", "")) == String(spec["nb"]))
var sv = _meta_new_save("user://smoke_meta_fork_rows.json")
var mp = load("res://core/meta_progression.gd").new(_db, sv)
sv.meta_state._add_keystone(1000)
Kit.check("同调位 1 购买受理(fork 前置行——根节点无前置)", mp.tree_buy("att_socket1"))
var bal0: int = sv.meta_state.keystone_balance()
Kit.check("fork_sense 购买受理(tree_buy_fork 真链——解锁+默认 A 态)",
mp.tree_buy_fork("fork_sense") and int(sv.meta_state.tree_state().nodes.get("fork_sense", 0)) == 1
and String(sv.meta_state.tree_state().fork_sides.get("fg_sense", "")) == "a")
Kit.check("fork_economy 购买受理(同链)",
mp.tree_buy_fork("fork_economy") and int(sv.meta_state.tree_state().nodes.get("fork_economy", 0)) == 1
and String(sv.meta_state.tree_state().fork_sides.get("fg_economy", "")) == "a")
Kit.check("两 fork 行扣币共 50(表行 price 驱动)", int(sv.meta_state.keystone_balance()) == bal0 - 50)
_meta_smoke_cleanup()
func _check_fork_side_projection() -> void:
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:30 (组侧投影与脉络行板——真实表行读出)
var sv = _meta_new_save("user://smoke_meta_fork_side.json")
var mp = load("res://core/meta_progression.gd").new(_db, sv)
sv.meta_state._add_keystone(1000)
Kit.check("投影夹具:同调位 1+fork_economy 购买受理", mp.tree_buy("att_socket1") and mp.tree_buy_fork("fork_economy"))
var fg_entry: Dictionary = {}
for g in (mp.tree_camp_status()["fork_groups"] as Array):
if String((g as Dictionary).get("group", "")) == "fg_economy":
fg_entry = g
Kit.check("fg_economy 组投影在册:display_name=财富之道 owned=true active_side=a〔QA余 V7〕",
String(fg_entry.get("display_name", "")) == "财富之道" and bool(fg_entry.get("owned", false))
and String(fg_entry.get("active_side", "")) == "a")
var sides: Array = fg_entry.get("sides", []) as Array
Kit.check("侧名投影:A 侧=聚(economy_hoard)/B 侧=散(economy_spend)〔QA余 V7〕",
sides.size() == 2 and String((sides[0] as Dictionary).get("display_name", "")) == "聚"
and ((sides[0] as Dictionary).get("effects", {}) as Dictionary).has("economy_hoard")
and String((sides[1] as Dictionary).get("display_name", "")) == "散"
and ((sides[1] as Dictionary).get("effects", {}) as Dictionary).has("economy_spend"))
Kit.check("A 态规则键=economy_hoard(聚)", (mp.tree_rules() as Dictionary).has("economy_hoard"))
Kit.check("切 b 受理(零币换侧)", mp.tree_toggle_fork("fg_economy", "b"))
Kit.check("B 态规则键=economy_spend(散)且 A 键退场〔QA余 V7〕",
(mp.tree_rules() as Dictionary).has("economy_spend") and not (mp.tree_rules() as Dictionary).has("economy_hoard"))
var spine_names := {}
for r in (mp.tree_camp_status()["rows"] as Array):
if String((r as Dictionary).get("vein", "")) == "spine":
spine_names[String((r as Dictionary).get("node_id", ""))] = String((r as Dictionary).get("display_name", ""))
Kit.check("spine 脉络投影读行:sp_hp=生命淬炼/sp_hit=命中淬炼(生产表行名非夹具喂值)〔QA余 V7〕",
String(spine_names.get("sp_hp", "")) == "生命淬炼" and String(spine_names.get("sp_hit", "")) == "命中淬炼")
_meta_smoke_cleanup()
@@ -17,7 +17,7 @@ var _ae: ActionEconomy
func _init() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:224 (排雷规则宿主——装载组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:224 (排雷规则宿主——装载组)
_db = load("res://core/config_db.gd").new() as ConfigDb
if not _db.load_tables("res://data/tables", false):
Kit.check("数值表加载", false)
@@ -46,7 +46,7 @@ func _init() -> void:
func _test_install_invariants() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:228 (装板四不变式组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:228 (装板四不变式组)
_rules.install_board(_board)
Kit.check("装板后 current_board 即所装棋盘", _rules.current_board() == _board)
var opened: int = _rules.revealed_cells().size()
@@ -70,7 +70,7 @@ func _unrevealed_safe() -> Vector2i:
func _test_dig_face_rules() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231 (挖/封印合法性与可辨拒绝组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:231 (挖/封印合法性与可辨拒绝组)
var revealed_first = _rules.revealed_cells()[0] as Vector2i
var out: Array = _rules.settle_batch([{"action": "dig", "cell": revealed_first}])
var o: Dictionary = (out[0] as Dictionary) if not out.is_empty() else {}
@@ -85,7 +85,7 @@ func _test_dig_face_rules() -> void:
func _test_reveal_advances() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:230 (揭示推进与连锁组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:230 (揭示推进与连锁组)
var c := _unrevealed_safe()
if c == Vector2i(-1, -1):
Kit.check("存在未揭示安全格(揭示推进前置)", false)
@@ -99,7 +99,7 @@ func _test_reveal_advances() -> void:
func _test_seal_place() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231 (重复封印拒绝组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:231 (重复封印拒绝组)
var c := _unrevealed_safe()
if c == Vector2i(-1, -1):
Kit.check("存在未揭示安全格(封印前置)", false)
@@ -112,7 +112,7 @@ func _test_seal_place() -> void:
func _test_reject_out_of_catalog() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:236 (目录外拒绝组)
# 判据出处: design/gdd/systems/排雷循环/排雷玩法.md:236 (目录外拒绝组)
var out: Array = _rules.settle_batch([{"action": "not_a_real_action", "cell": Vector2i(0, 0)}])
var o: Dictionary = (out[0] as Dictionary) if not out.is_empty() else {}
Kit.check("目录外意图被拒(不静默吞掉)——实际载荷 %s" % str(o).substr(0, 80),
@@ -122,7 +122,7 @@ func _test_reject_out_of_catalog() -> void:
# ---- V14 棋盘揭示预览只读口(架构整改——preview_reveal 与实挖揭示集同口径、零状态写入零事件)----
func _test_preview_reveal_readonly() -> void:
# oracle: design/engineering/architecture/architecture.md:103 (preview_reveal 只读预览口——零写入零事件组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·核心层」minesweeper_rules 行 (preview_reveal 只读预览口——零写入零事件组)
var events := 0
_rules.cell_revealed.connect(func(_evt): events += 1) # 零发射哨兵(preview 全程不触发揭示事件)
# 形态 A:封印在场——preview(未开格) 不越封印且零状态写入
+11 -11
View File
@@ -16,7 +16,7 @@ var _gen
func _init() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
_db = ConfigDBScript.new()
var load_ok: bool = _db.load_tables("res://data/tables", true)
if not load_ok:
@@ -78,7 +78,7 @@ func _max_depth(g: Dictionary) -> int:
# ---- V1 产出合格:三档×种子扫描,自证零错误 ----
func _test_output_ok() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:129 (产出约束六条+结构铁律组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点图生成.md:129 (产出约束六条+结构铁律组)
var all_ok := true
var entry_exit_ok := true
for L in [1, 2, 3]:
@@ -96,7 +96,7 @@ func _test_output_ok() -> void:
# ---- V3 可复现:纯函数+counter 无关 ----
func _test_reproducible() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:134 (同种子同参数同图组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点图生成.md:134 (同种子同参数同图组)
var same := true
for L in [1, 2, 3]:
if _sig(_graph(L, 42)) != _sig(_graph(L, 42)):
@@ -127,7 +127,7 @@ func _test_reproducible() -> void:
# ---- V5 岔路取舍两条 ----
func _test_fork_rules() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:88 (岔路取舍组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点图生成.md:88 (岔路取舍组)
var next_hop_ok := true
var comp_ok := true
for L in [1, 2, 3]:
@@ -203,7 +203,7 @@ func _branch_types(g: Dictionary, head_id: int, sibling_outs: Array) -> Array:
# ---- V6 必经恰一店+归属锁定+多终点深度互异 ----
func _test_shop_and_attribution() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:48 (必经恰一店与归属锁定组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点图生成.md:48 (必经恰一店与归属锁定组)
var paths_ok := true
var lock_ok := true
var depth_ok := true
@@ -256,7 +256,7 @@ func _all_paths(g: Dictionary) -> Array:
# ---- V7 战斗保底 ----
func _test_battle_floor() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:129 (战斗保底组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点图生成.md:129 (战斗保底组)
var ok := true
for L in [1, 2, 3]:
for s in range(42, 52):
@@ -268,7 +268,7 @@ func _test_battle_floor() -> void:
# ---- V9 含岔口与出口结构 ----
func _test_exit_structure() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:48 (出口结构与分支互斥组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点图生成.md:48 (出口结构与分支互斥组)
var fork_all := true
var l1_single := true
var big_fork_ok := true
@@ -315,7 +315,7 @@ func _terminals_reachable(g: Dictionary, id: int) -> int:
# ---- V9 Boss 出场门控(难度曲线与Boss——每区域恰一 Boss 终点)----
func _test_boss_exit_per_region() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:48 (每区域 Boss 出口组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点图生成.md:48 (每区域 Boss 出口组)
# 真实生成多区域图:全部终点 type=="boss"、boss 终点数==exit_count、深度=L−r(r∈[0,exit_count)
# ——每区域恰一 boss、深度互异即分属不同区域)
# 节点 depth 字段=入口起最长路径深(生成器校验轴);区域序号 r 的可观测形态=最深 boss(r=0)
@@ -351,7 +351,7 @@ func _test_boss_exit_per_region() -> void:
# ---- V10 RNG 单点(纯流面 counter 不变)----
func _test_rng_single_point() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:134 (RNG 单点流组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点图生成.md:134 (RNG 单点流组)
var svc = RngScript.new()
svc.new_adventure(4242)
var before: int = svc.counter()
@@ -366,7 +366,7 @@ func _test_rng_single_point() -> void:
# ---- V1 违规夹具:各报对应错误类别 ----
func _test_negative_fixtures() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:198 (无解 fail-fast 夹具组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:198 (无解 fail-fast 夹具组)
# 夹具构造器:手写 nodes/entries/terminals
var dup := _fx([
{"id": 0, "type": "battle", "depth": 0, "next": [1]},
@@ -493,7 +493,7 @@ func _has_msg(errs: Array, needle: String) -> bool:
# ---- V6 节点图三参数表值驱动(架构整改——fork_third_way_pct/fill_rest_first_pct/width_max 入 level_gen_params)----
func _test_gen_params_table_driven() -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:129 (参数档读表驱动组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点图生成.md:129 (参数档读表驱动组)
# ① 同种子真实表行产图逐位(表值驱动回归锚)
var g1 := _graph(2, 42)
var g2 := _graph(2, 42)
+5 -5
View File
@@ -12,7 +12,7 @@ const Kit := preload("res://tests/unit/test_kit.gd")
func _init() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
if OS.get_cmdline_user_args().has("fatal-fixture"):
_trigger_fatal()
return
@@ -39,7 +39,7 @@ func _reading(gen, db, L: int) -> Dictionary:
# ---- V8 生长递增(三档×多种子逐档读数)----
func _test_growth(gen, db) -> void:
# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:130 (生长递增组)
# 判据出处: design/gdd/systems/冒险循环/关卡节点图生成.md:130 (生长递增组)
var steps_ok := true
var count_ok := true
for s in [42, 43, 44, 45, 46]:
@@ -63,7 +63,7 @@ func _test_growth(gen, db) -> void:
# ---- V4 参数无解判定(静态面:域/预算无解夹具必须被 check_params 拦下)----
func _test_fail_fast_static(gen, db) -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:198 (参数无解静态拒绝组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:198 (参数无解静态拒绝组)
Kit.check("生产三档参数档可行性判定零错误〔fv4〕",
NodeMapGenerator.check_params(db.run.level_gen_params.row_by_level_index(1)).is_empty()
and NodeMapGenerator.check_params(db.run.level_gen_params.row_by_level_index(3)).is_empty())
@@ -82,7 +82,7 @@ func _test_fail_fast_static(gen, db) -> void:
# ---- V4 致命中止真实通道(子进程退出码非零;零回退分支=件核)----
func _test_fatal_subprocess() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 (无解致命子进程组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:199 (无解致命子进程组)
var bin := _engine_bin()
if bin == "":
Kit.check("未找到引擎二进制,致命子进程通道未跑(如实标注)〔fv4〕", false)
@@ -98,7 +98,7 @@ func _test_fatal_subprocess() -> void:
func _trigger_fatal() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var gen = GenScript.new(RngScript.new())
# 域内合法但深度预算无解:层深 6 放三出口(check_params 拦下 → 致命中止)
var unsolvable := {"layers": 6, "branch_density": 0.0, "battle_ratio": 0.5, "reward_ratio": 0.2,
@@ -13,7 +13,7 @@ func _init() -> void:
func _smoke_required_injection() -> void:
# oracle: design/engineering/code-conventions.md:40 (拆分交接:依赖构造期一次交付——日志器必填注入守卫组)
# 判据出处: design/engineering/code-conventions.md:40 (拆分交接:依赖构造期一次交付——日志器必填注入守卫组)
# 未注入(缺省 0):start_session 返回空串、零文件产出
var lg0 = load("res://presentation/playtest_logger.gd").new()
var p0: String = lg0.start_session(42)
+5 -5
View File
@@ -9,7 +9,7 @@ const Kit := preload("res://tests/unit/test_kit.gd")
func _init() -> void:
# oracle: design/engineering/architecture/architecture.md:107 (池结构案 D:候选抽取与池纪律组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·核心层」CurioDeck 行 (池结构案 D:候选抽取与池纪律组)
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
print("[pool_case_d_test] FAIL — 数值表加载失败")
@@ -66,7 +66,7 @@ func _tiers_of(cands: Array) -> Array:
func check_v7(a, db) -> void:
# oracle: design/engineering/architecture/architecture.md:107 (池案 D V7 组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·核心层」CurioDeck 行 (池案 D V7 组)
if not a.start_adventure(201):
Kit.check_quiet(false, "V7 前置:冒险启动")
return
@@ -112,7 +112,7 @@ func check_v7(a, db) -> void:
func check_v6(b, db) -> void:
# oracle: design/engineering/architecture/architecture.md:107 (池案 D V6 组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·核心层」CurioDeck 行 (池案 D V6 组)
if not b.start_adventure(202):
Kit.check_quiet(false, "V6 前置:冒险启动")
return
@@ -142,7 +142,7 @@ func check_v6(b, db) -> void:
func check_v12(m, db) -> void:
# oracle: design/engineering/architecture/architecture.md:107 (池案 D V12 组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·核心层」CurioDeck 行 (池案 D V12 组)
if not m.start_adventure(401):
Kit.check_quiet(false, "V12 前置:冒险启动")
return
@@ -161,7 +161,7 @@ func check_v12(m, db) -> void:
func check_v8(c, db) -> void:
# oracle: design/engineering/architecture/architecture.md:107 (池案 D V8 组)
# 判据出处: design/engineering/architecture/architecture.md「模块归属·核心层」CurioDeck 行 (池案 D V8 组)
if not c.start_adventure(203):
Kit.check_quiet(false, "V8 前置:冒险启动")
return
+6 -6
View File
@@ -20,7 +20,7 @@ const TAG_OK := "board_gen" # 在册合法标签(PURPOSE_TAGS 含;wr7 必
func _init() -> void:
# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 (运行期违约通道——致命真的会死组)
# 判据出处: design/gdd/systems/表现与支撑/数据配置.md:199 (运行期违约通道——致命真的会死组)
var db = ConfigDBScript.new()
if not db.load_tables("res://data/tables", true):
push_error("[run_time_fatal_test] 表加载失败——夹具不可用(回归)")
@@ -45,7 +45,7 @@ func _init() -> void:
# 缺省形态(契约 2):不接组装点的协作件违约只落自身 push_error+assert,不外溢封印
func _default_form(db) -> bool:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var rng = RngScript.new()
rng.set_master_seed(3)
var oc = OrchestratorScript.new(db, rng)
@@ -70,7 +70,7 @@ func _default_form(db) -> bool:
# (棋盘生成半边:装板域拆件后生成器随 BoardInstaller 件内自建——组装点=引用束 fatal_cb 接线,
# 经新组装点构建后触发生成器违约,封印传导断言不变)
func _seal_propagation(db) -> bool:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var rng_a = RngScript.new()
rng_a.set_master_seed(5)
var oc_bg = OrchestratorScript.new(db, rng_a)
@@ -92,7 +92,7 @@ func _seal_propagation(db) -> bool:
# ②wr6(p6):未知用途标签 → handler 记录位为真(判据主体)+拿不到可用 RNG;合法标签对照另行验过
func _unknown_tag_channel() -> bool:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var rng_bad = RngScript.new()
rng_bad.set_master_seed(7)
if not _inject_handler(rng_bad, "wr6"):
@@ -111,7 +111,7 @@ func _unknown_tag_channel() -> bool:
# ③wr7(p7):未播种取数 → handler 记录位为真+拿不到可用 RNG;同标签播种对照另行验过
func _unseeded_channel() -> bool:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
var rng_bad = RngScript.new() # 不播种(_master_seed 缺省 -1)
if not _inject_handler(rng_bad, "wr7"):
return false
@@ -129,7 +129,7 @@ func _unseeded_channel() -> bool:
# 成员在位前提检查(成员被移除时走干净退出 2,不落「赋值不存在的属性」式中途中断)
func _inject_handler(rng, label: String) -> bool:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
if not (rng.get("violation_handler") is Callable):
push_error("[run_time_fatal_test/%s] violation_handler 成员缺失(p6/p7 形态未实装——回归)" % label)
return false
@@ -22,7 +22,7 @@ func _init() -> void:
# ① 无参实例(autoload 实例化形态)=无激活态+默认合法空态(sv2 类级)
func _test_no_arg_instance_has_no_activation() -> void:
# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (启动态无激活不读写组)
# 判据出处: design/gdd/systems/表现与支撑/存档序列化.md:61 (启动态无激活不读写组)
var sm := SaveManagerScript.new()
Kit.check_quiet(sm.slots.active_slot() == 0 and not sm.slots.has_write_target(),
"sv2 无参实例无激活态(路径空/激活号 0/写盘目标空)〔卡① sv2〕")
@@ -41,7 +41,7 @@ func _test_no_arg_instance_has_no_activation() -> void:
# 判别锚:空 _path 的退化写目标=裸 ".tmp"(落 res://,拆闸实验实测杂散落点)——
# 断言先清退化目标再核零新增,闸短路缺陷(拒绝口旁路、写盘真实发生)必红。
func _test_write_rejected_without_target() -> void:
# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (无激活态写盘拒绝组)
# 判据出处: design/gdd/systems/表现与支撑/存档序列化.md:61 (无激活态写盘拒绝组)
var sm := SaveManagerScript.new()
var before := {}
for i in range(1, 4):
@@ -81,7 +81,7 @@ func _test_write_rejected_without_target() -> void:
# (生产结算链全段在位:mark_first_adventure_done→apply_adventure_summary→refresh_treasure_unlocks
# →write_to_disk;无激活态下落账内存、写盘拒绝、盘面零触碰——已知中间态的机检面)
func _test_settle_chain_write_rejected() -> void:
# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (结算链写盘拒绝组)
# 判据出处: design/gdd/systems/表现与支撑/存档序列化.md:61 (结算链写盘拒绝组)
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
Kit.check_quiet(false, "sv3 链测试前置:数值表加载失败〔卡① sv3〕")
@@ -104,7 +104,7 @@ func _test_settle_chain_write_rejected() -> void:
# ③ 越界槽号三原语拒绝+窥视越界返回不可读(原语边界 fail-safe)
func _test_out_of_range_slots_rejected() -> void:
# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (三存档位域界——越域槽拒绝组)
# 判据出处: design/gdd/systems/表现与支撑/存档序列化.md:61 (三存档位域界——越域槽拒绝组)
var sm := SaveManagerScript.new()
sm.slots.attach_slot_template("user://smoke_boot_noact_%d.json")
Kit.check_quiet(not sm.slots.activate_slot(0) and not sm.slots.activate_slot(4)
@@ -19,7 +19,7 @@ var _db
func _init() -> void:
# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
_db = ConfigDBScript.new()
# 表加载失败不中止(承 G2–G5 同款:表损坏的红落在具体断言上)
var load_ok: bool = _db.load_tables("res://data/tables", true)
@@ -47,7 +47,7 @@ func _fresh_rng(seed_val: int):
# 损坏兜底/迁移链/域界干净/摘要 cap/摘要跨系统块/生涯终身计数域 v6)。测试文件统一 user://smoke_save_*.json 隔离,
# 段首清场+段尾清理——绝不碰 user://save.json(红线)。
func _smoke_save() -> void:
# oracle: design/gdd/systems/表现与支撑/存档序列化.md:176 (存档域读写往返/迁移链/损坏兜底/域界组)
# 判据出处: design/gdd/systems/表现与支撑/存档序列化.md:176 (存档域读写往返/迁移链/损坏兜底/域界组)
print("--- SaveManager 存档读写/迁移/兜底 ---")
var sm_script = load("res://core/save_manager.gd")
var paths := ["user://smoke_save_roundtrip.json", "user://smoke_save_fresh.json",
@@ -340,7 +340,7 @@ func _smoke_save() -> void:
# 测试文件统一 user://smoke_save_slot_%d.json 模板隔离(attach_slot_template 注入口——禁各自拼槽名),
# 段首清场+段尾清理——绝不碰 user://save.json 与真实槽 user://save_slot_N.json(红线)。
func _smoke_save_slots() -> void:
# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (三槽位与摘要 cap——槽位域组)
# 判据出处: design/gdd/systems/表现与支撑/存档序列化.md:61 (三槽位与摘要 cap——槽位域组)
print("--- SaveManager 三存档位承载(迭代 33 卡①)---")
var sm_script = load("res://core/save_manager.gd")
var slot_tpl := "user://smoke_save_slot_%d.json"
Loaded 100 of 165 files, more files were not shown because too many files have changed in this diff. Show more