refactor: production 解散+docs 并入 design+tests 归 game 三层收敛——active.md/roadmap/stage/review-mode 升根目录;qa 报告→design/qa(gate-checks/alpha 验收/任务书→research);docs/→design/dev+registry 并档+散提案→research;tests/→game/tests(空架子 README+示例,真测试在 game/smoke);game/tools→tools+样例输出→sim-data;qa-data→sim-data;design/ux→gdd/ux;删 loop-dashboard.html(可再生成)+route_overlay_preview(零引用);钩子三件+loop_dashboard/balance_verifier/render_sample_sheet 路径随迁;断链修复 12 文件,余 7 条全为搬前幽灵链

This commit is contained in:
DKChan committed 2026-09-27 04:56:27 +08:00
1 parent 97e20d51fb
commit 7f24b640ea
179 files changed
+81 -579

No files matched your search

@@ -0,0 +1,129 @@
# ADR-0001: 事件总线与编排载体(Event Bus & Orchestration Carrier)
## Status
Accepted(2026-09-12,solo 模式用户授权代决;用户迭代期可随时推翻重议——推翻时须走本 ADR 修订 + 下游 AC 连锁复核)
## Date
2026-09-12
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Godot 4.7.2-stable |
| **Domain** | Core / Scripting(Signal/Callable/生命周期) |
| **Knowledge Risk** | LOW(4.x 基线稳定;4.5–4.7 变更清单零命中,godot-specialist 校验通过) |
| **References Consulted** | `docs/engine-reference/godot/`(VERSION / breaking-changes / deprecated-apis / current-best-practices);godotengine/godot#6360 |
| **Post-Cutoff APIs Used** | None |
| **Verification Required** | None(切片 88/88 冒烟已实证:双源 round_end 汇聚、可回放步骤流) |
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | None |
| **Enables** | ADR-0003(场景结构引用本 ADR 的接线纪律);A2/B1 全部事件序类 AC 从纸面契约转为可执行 |
| **Blocks** | 正式工程骨架搭建(架构文档 Required ADRs 条件) |
| **Ordering Note** | 与 ADR-0008(切片机制正式化)并行无冲突;B1 结算触发语义以本 ADR 载体承载 |
## Context
### Problem Statement
A2(三事件外发)、A3(stopped 直发 round_end,双发射源)、B1(结算触发 = Waiting 相位编排点、不依赖事件到达序)之间的信号载体需要一个落地形态——GDD 层只定行为契约(A2 OQ2 / B1 OQ1 均登记为"实现期 ADR 确认项")。
### Constraints
- 单线程回合制(D2 EC13);Core 层零 UI 依赖、headless 可跑(D2 Rule 7)
- 结算可回放是硬承诺(B1 Core Rule 15:禁 await、零随机)
- 事件序契约:`flag_judged` < `round_end` 全路径统一,但到达序不承载语义
- RefCounted 纯逻辑类无场景表示,无法在 .tscn 编辑器里对接信号
### Requirements
- 支持 A2 三事件面 + A3 stopped 直发的双发射源汇聚
- 支持多订阅者(B1 facts 缓冲;呈现层只订 B1/runner)
- 测试可捕获事件序列(A2/B1 AC 的事件捕获断言面)
- 生命周期确定(无悬挂连接、无循环引用)
## Decision
**组合根直连 signal 接线 + B1 Waiting 相位触发;否决 autoload 全局事件总线。**
1. **信号载体**:A2(RefCounted)声明并发射 GDD 三事件——`cell_revealed` / `flag_judged` / `round_end_board`(GDD 概念名 `round_end` 棋盘路径的实现实名);A3 停手路径发射 `round_end_stopped`(payload 与棋盘路径同构)。回发 `next_round_mode` / `battle_over` 同为 A3/B1 侧 signal。
2. **接线位置**:全部 signal.connect 发生在**组合根**(主场景 Node 的装配代码)——core↔core 无静态耦合,依赖单向由接线拓扑保证。RefCounted 无 .tscn 编辑器对接能力,组合根是唯一自然落点。
3. **B1 触发**:订阅 A3 相位变更(→Waiting)触发结算事务;触发判定只读 A3 相位观测量与 `last_round_end`,**不消费事件到达序**(同步发射 + 双发射源的正确防御—— specialist 确认信号为同步发射、多订阅者投递序 = 连接序 FIFO)。
4. **订阅纪律**:呈现层**永不直接订阅 A2/A3**——只订 B1 步骤流 / runner(切片现状固化,防呈现层绕过编排读核心生命周期状态——切片缺陷②教训)。
### Architecture Diagram
```
[A2 minesweeper_rules]──cell_revealed/flag_judged/round_end_board──┐
[A3 action_economy]────round_end_stopped / phase→Waiting──────────┤
▼
[组合根 main 装配接线]→[B1 battle_orchestrator]
│ 步骤流行协议
▼
[D1 视图单元(只订 B1/runner)]
```
### Key Interfaces
- 信号均为 **方法 Callable 连接**(见禁令);信号参数带类型标注仅作文档,无运行时检查——payload 校验由消费侧断言承载(A2 AC 前言口径)。
- A2 事件捕获测试 API:冒烟经 signal connect 到捕获数组(AC 前言"事件捕获断言面"的落地形态)。
## Alternatives Considered
### Alternative 1: autoload 全局事件总线
- **Description**: 中心 autoload 单例持有全部具名信号,系统经总线收发。
- **Pros**: 解耦发射者/订阅者;全局可发现。
- **Cons**: 全局耦合(任何系统可达任何信号——与"依赖单向"原则冲突);单线程回合制无此需求;事件溯源变难。
- **Rejection Reason**: 过度设计;切片直连形态已验证且更简单。
### Alternative 2: 轮询(B1 每帧查 A3 相位)
- **Description**: B1 在 process 中轮询 A3 相位观测量。
- **Cons**: 延迟(帧粒度)+ 空转;与"回合制无逐帧玩法逻辑"原则冲突。
- **Rejection Reason**: 无收益;信号已同步即时。
### Alternative 3: 回调注册表(A2 持有 handler 列表)
- **Description**: A2 暴露 register(handler) API,非 signal。
- **Cons**: 非 GDScript 惯用;多订阅者接线繁琐;丢掉引擎信号工具链(.tscn 对接、调试器可视)。
- **Rejection Reason**: signal 即本用途的引擎原生方案。
## Consequences
### Positive
- 事件生命周期**确定性优于 Node 方案**:RefCounted 引用计数归零即双向自动断连,无 queue_free 帧末窗口(specialist 核实)——不存在"向半死对象发射"
- 组合根接线使依赖方向可视化、可审计;切片 88/88 已验证
### Negative
- 组合根承担全部接线职责——视图单元增多时接线代码线性增长(切片 main.gd 433 行已容纳;演进为装配器函数分组即可)
- 信号同步发射的再入语义需实现者理解(本 ADR 的"不依赖到达序"即防御)
### Risks
- **lambda 捕获 self 的循环引用**:GDScript lambda 捕获持强引用——短命 RefCounted 以捕获 self 的 lambda 连到长命发射者会延长生命周期乃至成环,自动断连永不触发 → **缓解(固化为本 ADR 静态规则)**:core↔core 信号连接只许方法 Callable;发射者禁止持有对订阅者的反向引用
- 信号参数无运行时类型检查 → 缓解:消费侧断言(既有 AC 口径)+ 呈现载荷 schema 成文义务(code-conventions)
## GDD Requirements Addressed
| GDD System | Requirement | How This ADR Addresses It |
|------------|-------------|--------------------------|
| a2-minesweeper-rules.md | Core Rule 2 三事件回边契约(发射面封闭);OQ2 事件外发载体 | signal 载体 + 实名映射(round_end→round_end_board)+ 组合根接线;捕获 API 承载 AC 断言面 |
| b1-settlement.md | Core Rule 2 结算触发 = Waiting 相位编排点;OQ1 事件总线载体 | 订阅 A3 相位触发、只读 last_round_end、不依赖到达序 |
| a3-action-economy.md | Rule 9 round_end 双发射源(stopped 直发、payload 同构) | round_end_stopped 独立信号,同构 payload 由组包纪律保证 |
| systems-index.md | 唯一回边裁决(A2 只外发、B4/B1 订阅) | 组合根接线拓扑 = 裁决的落地形态 |
## Performance Implications
- **CPU**: 信号发射 = 同步调用链,回合制下每事务个位数次发射——可忽略
- **Memory**: 方法 Callable 弱引用目标、不延长生命周期;lambda 禁令消除成环风险
- **Load Time**: 无
- **Network**: 不适用
## Migration Plan
切片即本决策的实现(`core/minesweeper_rules.gd` 三信号、`action_economy.gd` round_end_stopped、`battle_orchestrator.gd` 双源汇聚)——无迁移,正式化 = 把接线纪律与静态规则写入扫描域(ADR-0004)。
## Validation Criteria
- A2/B1 冒烟事件捕获断言面全绿(既有 88 条覆盖)
- 静态扫描:core↔core 连接零 lambda;发射者零反向引用(ADR-0004 扫描域承载)
- 回放确定性:同种子同输入步骤流逐行相同(B1 AC-33 先例,切片已证)
## Related Decisions
- ADR-0003(场景结构与组合根)、ADR-0004(测试载体与静态扫描域)
- docs/architecture/architecture.md(Data Flow §2 事件路径)
@@ -0,0 +1,119 @@
# ADR-0002: 渲染器与窗口配置(Renderer & Window Config)
## Status
Accepted(2026-09-12,solo 模式用户授权代决;迭代期可推翻重议)
## Date
2026-09-12
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Godot 4.7.2-stable |
| **Domain** | Rendering |
| **Knowledge Risk** | MEDIUM(渲染域为 4.5–4.7 变更集中地——本 ADR 相关项已逐条核实,godot-specialist 校验通过) |
| **References Consulted** | `docs/engine-reference/godot/`(四文件);Godot 4.3 release notes(Compatibility 特性补齐);Godot Docs Environment and post-processing |
| **Post-Cutoff APIs Used** | None |
| **Verification Required** | 首次正式导出时核对 gl_compatibility 在目标硬件清单上的实际表现(首次 release 导出 checklist 既有项) |
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | None |
| **Enables** | ADR-0003(工程骨架的 project.godot 基线);D1 正式化 |
| **Blocks** | 正式工程骨架搭建 |
| **Ordering Note** | B5 战斗表现若需 HDR 2D 届时复议(见 Risks) |
## Context
### Problem Statement
technical-preferences 将渲染器选择(Forward+ vs gl_compatibility)登记为实现期 ADR;project.godot 的窗口/stretch 基线需要显式定稿——4.7 新项目默认值与训练数据不同(stretch mode canvas_items / aspect expand / Windows 驱动 D3D12),必须显式落盘防漂移。
### Constraints
- 2D 回合制;draw calls < 200 预算;无 3D、无移动端
- headless 测试链路不初始化渲染器(dummy DisplayServer)——测试与渲染器解耦
- 引擎知识 MEDIUM 风险:渲染域选型必须基于已核实清单
### Requirements
- 满足视觉锚(数字可读性、双通道、绘制优先级)所需的渲染能力
- PC(Steam)硬件门槛尽量低
- project.godot 各键显式写入(防未来 config_version 升级漂移)
## Decision
**渲染器 = gl_compatibility;窗口基座 1280×800(切片已验证值)、stretch mode = canvas_items、aspect = expand;全部键显式写入 project.godot。**
```ini
rendering/renderer/rendering_method="gl_compatibility"
rendering/renderer/rendering_method.mobile="gl_compatibility" ; PC 无关,显式对齐防混淆
display/window/size/viewport_width=1280
display/window/size/viewport_height=800
display/window/stretch/mode="canvas_items"
display/window/stretch/aspect="expand"
; display/window/dpi/allow_hidpi —— 4.x 默认 true,无需写入(勿关闭)
```
- **否决 Forward+**:2D 重 UI 无收益;4.6 起 Windows 新项目 D3D12 默认只作用于 Vulkan 系(Forward+/Mobile)——选 gl_compatibility(OpenGL 3.3)**完全绕开 D3D12 新驱动线**,这是 MEDIUM 风险域里的降险选择;GPU 门槛最低。
- **stretch/aspect 与 4.7 新项目默认一致**(canvas_items/expand)——仍显式落盘,防引擎默认未来变化。
- **基座 1280×800** = 切片验证值(非引擎默认 1152×648,亦非本 ADR 初稿设想的 1920×1080);expand 使 16:9 全屏显示正常适配,D1 正式化时如需调整走本 ADR 修订。
### Architecture Diagram
(不适用——配置决策;headless dummy DisplayServer 与本决策完全解耦,冒烟链路不受影响)
### Key Interfaces
- project.godot 键集如上(显式全落盘);渲染器相关代码面 = CanvasItem/Control 绘制(三渲染器共用管线,重 UI 场景 Compatibility 开销最低——specialist 核实)。
## Alternatives Considered
### Alternative 1: Forward+(Vulkan/D3D12)
- **Description**: 4.7 新项目默认渲染器。
- **Pros**: HDR 2D、高级后处理全量可用。
- **Cons**: 2D 回合制无收益;D3D12 为 4.6 起新默认驱动线(成熟度风险敞口);GPU 门槛更高。
- **Rejection Reason**: 零收益 + 增加风险敞口;B5 若需 HDR 2D 届时复议。
### Alternative 2: 沿用引擎新项目默认不显式配置
- **Cons**: 渲染方法默认 forward_plus;基座默认 1152×648 与切片验证值不符;未来 config_version 升级可能漂移。
- **Rejection Reason**: 违背"显式落盘防漂移"要求。
## Consequences
### Positive
- 绕开 D3D12 驱动线与 Vulkan 门槛——MEDIUM 风险域降险
- headless 测试与渲染器解耦(已核实),CI 零 GPU 依赖
- **B5 复议空间存在**:Compatibility 自 4.3 起已支持 2D 灯光+阴影与 glow/bloom——多数 2D 表现需求无需离开本渲染器
### Negative
- 无 HDR 2D(Forward+/Mobile 专属);glow 实现不同且部分属性隐藏——B5 视觉上限受此约束
- Compatibility 已知渲染问题两条登记(GH-110670 HDR2D+tonemap glow 压暗 / GH-90259 开阴影灯光变亮)——B5 采用相关特性时核对
### Risks
- B5 需要超出现有能力的表现 → 缓解:复议通道 = 本 ADR 修订(迁移成本 2D 项目可控)
- OpenGL 3.3 在极旧硬件的覆盖 → 缓解:目标平台 PC Steam 主流硬件;首次导出 checklist 核对
## GDD Requirements Addressed
| GDD System | Requirement | How This ADR Addresses It |
|------------|-------------|--------------------------|
| technical-preferences.md | Renderer 选择留实现期 ADR;draw calls < 200 | gl_compatibility + 自绘/控件混合渲染在此预算内宽裕 |
| game-concept.md | 视觉锚三条硬约束 | canvas_items stretch 保证数字缩放清晰;Compatibility 2D 管线满足绘制优先级栈 |
| d2-data-config.md | Rule 7 headless 工程债 | dummy DisplayServer 与渲染器解耦(已核实) |
## Performance Implications
- **CPU**: Compatibility 2D 管线开销最低(重 UI 场景——specialist 核实)
- **Memory**: 三渲染器中最低
- **Load Time**: 无差异
- **Network**: 不适用
## Migration Plan
切片 project.godot 已写 gl_compatibility——正式工程直接继承;补齐基座尺寸与 stretch 键显式落盘。
## Validation Criteria
- draw calls 实测 < 200(D1 集成时用编辑器 profiler 核对)
- 16:9 全屏 / 窗口两种形态下数字可读性目检(视觉锚 1)
- headless 冒烟不受渲染器影响(88/88 维持)
## Related Decisions
- ADR-0003(场景结构——project.godot 同批落盘)、ADR-0010(B5 战斗舞台,未立项)
- docs/engine-reference/godot/current-best-practices.md §1(新项目默认值差异)
@@ -0,0 +1,139 @@
# ADR-0003: 场景结构与棋盘渲染(Scene Structure & Board Rendering)
## Status
Accepted(2026-09-12,solo 模式用户授权代决;迭代期可推翻重议)
**部分修订(2026-09-23)**:Decision 3 棋盘渲染形态条款由 [ADR-0006](adr-0006-board-node2d-rendering.md) 部分取代——棋盘本体定向迁 Node2D 渲染(D 区 Phase A 先行,B 区舞台后续批),「禁盲目迁移」条款解除,其登记的探针耦合成本转为迁移批义务(实证收敛为 `_cells` 键面 + `click_cell` + `cell_size` 三点,公共面契约保持即可零改造)。Alternative 2(逐格 Node2D)的否决理由「无必要」因表现层升级需求不再成立;autoload 清单、类形态、CJK 字体前置等其余条款不变。
## Date
2026-09-12
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Godot 4.7.2-stable |
| **Domain** | Core / Scripting + UI |
| **Knowledge Risk** | LOW-MEDIUM(结构决策 LOW;渲染呈现项 MEDIUM——CanvasItem 线条 AA 羽化移除已核实,godot-specialist 校验含 2 项实质修正后通过) |
| **References Consulted** | `docs/engine-reference/godot/`(四文件);godotengine/godot#6360(--script 无 autoload) |
| **Post-Cutoff APIs Used** | None |
| **Verification Required** | CJK 字体随包发行的显示核对(首次正式导出 checklist 增补项);D1 正式化时若执行 _draw 迁移则另核命中测试/无障碍 |
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | ADR-0001(组合根接线纪律)、ADR-0002(project.godot 基线同批落盘) |
| **Enables** | 正式工程骨架搭建;D1 正式化 |
| **Blocks** | ADR-0004 扫描域(引用本 ADR 的 core 隔离规则) |
| **Ordering Note** | 棋盘渲染形态裁决(本 ADR §Decision 3)与 ADR-0004 探针耦合——见 Decision 3 的迁移工作项 |
## Context
### Problem Statement
正式工程骨架需要定稿:autoload 清单、core/presentation 的类形态与目录结构、棋盘渲染的实现路径。切片已验证一套形态,但初稿 ADR 把"自绘 _draw()"当作切片事实——godot-specialist 校验发现切片 BoardView 实为 **GridContainer + 逐格常驻控件**,本 ADR 按校验修正。
### Constraints
- Core 层零 UI 依赖、headless 可跑(D2 Rule 7);--script 模式下 core 禁裸引用 autoload 全局标识符(编译期即炸——见 ADR-0004)
- 同屏 draw calls < 200;棋盘 ≤12×12(MVP 量级)+数字/小人/旗/变异多层叠加(视觉锚绘制优先级栈)
- 领域命名规范(docs/code-conventions.md:禁 ID 前缀、头注溯源)
### Requirements
- 切片验证过的结构不推倒(用户拍板"按常规老路子")
- 呈现层与核心的写边界:核心写调用唯一发生在组合根
- 中文文案可显示(运行时默认字体无 CJK 字形——specialist 核实)
## Decision
**1. autoload 清单 = {ConfigDB, RngService}(定名锁定),仅此两个。**
战斗编排器等核心类为普通类、由组合根按局实例化——非 autoload、非场景节点。
**2. 类形态与目录结构:core/ 纯逻辑类 = RefCounted;ConfigDB/RngService = autoload Node 单例(引擎约束:autoload 脚本必须 extends Node);presentation/ = Control 派生视图单元。**
```
res://
├── core/ # RefCounted 纯逻辑(board_generator / minesweeper_rules /
│ # action_economy / battle_orchestrator / unit_container /
│ # status_container / reward_economy / level_runner / 数据类)
│ # —— 零 Control 引用、零 .tscn preload、零 autoload 全局标识符裸引用
├── data/
│ ├── schemas/ # D2 schema 类(Resource 派生——D2 体系,非本 ADR 变更项)
│ └── tables/ # .tres 数值表(单一事实源)
├── presentation/
│ ├── views/ # 九单元:BoardView / HUDView / BattleLogView / ItemWindowOverlay /
│ │ # NodeOverlay / LevelStripView / TunePanel / UiKit
│ ├── hud_presenter.gd
│ └── main.tscn + main.gd # 组合根:模式流转 + 信号接线 + 核心写调用唯一落点
├── smoke/ # 冒烟脚本 + 探针场景(ADR-0004)
└── docs/architecture/
```
**3. 棋盘渲染形态:MVP 沿用切片已验证形态——GridContainer + 逐格常驻控件(BoardView 持 `_cells`,refresh 内逐格更新);自绘 `_draw()` 迁移登记为 D1 正式化候选工作项(非本 ADR 承诺)。**
- specialist 校验事实:切片 BoardView = GridContainer + 逐格 Button(`_paint_cell` 逐格更新),三探针与该形态耦合(ui_probe 断言 `_cells` 非空、click_probe 依赖 `click_cell` 入口)。初稿"_draw 是切片形态的正式化"**不成立**,按事实修正。
- 形态对比留档:逐格控件(现状)= 命中测试/悬停/焦点由引擎承担、探针兼容;代价 = 控件树随格数增长(12×12=144 控件,实测无压力)。`_draw()` = 单 draw call、绘制优先级栈完全可控;代价 = 自建命中测试/悬停 + **三个探针改造**(`_cells` 断言与 `click_cell` 入口失效)+ 无障碍面缺失 + 需补 `Viewport.push_input` 端到端输入探针。
- 裁决:D1 正式化 GDD 评审时按当时视觉需求(biome 色板、雾纹理、动效复杂度)二选一;在此之前禁盲目迁移(探针改造是隐藏成本)。任一形态下 CanvasItem 线条 AA 羽化移除(4.7)需注意——格边框用填充 `draw_rect` 不受影响。
**4. CJK 字体前置(specialist 补登,两种形态都受影响)**:Godot 运行时默认字体不含 CJK 字形——全部中文文案(按钮/标签/结算明细)必须**随包发行 CJK 字体并设为 theme 默认字体**。登记为 D1 正式化硬性前置项 + 首次导出 checklist 项。
### Architecture Diagram
见 `docs/architecture/architecture.md` System Layer Map + Module Ownership(本 ADR 是其工程形态落点)。
### Key Interfaces
- 组合根 = 唯一核心写调用落点(settle_batch / submit_window_selection / runner 推进)——切片纪律转正
- 视图单元 = 信号外发 + setter 只读注入(BoardView 只读 A2、动作信号外发不碰核心写)
## Alternatives Considered
### Alternative 1: 战斗编排器设为 autoload
- **Cons**: 编排器持有跨局状态却永生——生命周期与战斗域契约冲突(Uninit/BattleOver 状态机要求按局重建);与"autoload = 全局基础设施"边界冲突。
- **Rejection Reason**: 切片形态(按局实例化)已验证且语义正确。
### Alternative 2: 棋盘 = 每格一个 Node2D
- **Pros**: 独立节点动画/模组方便。
- **Cons**: 每节点处理开销与 Control/Node2D 混管复杂度;无必要(specialist 指出 2D 同材质有批处理,draw call 论据最弱,但节点开销与混管论据成立)。
- **Rejection Reason**: 逐格控件已覆盖需求且已验证。
### Alternative 3: TileMapLayer 承载棋盘
- **Cons**: 4.5 物理分块默认变更(纯视觉不踩,但物理用途即触);瓦片语义与"变体标记/数字/旗多层叠加"不匹配。
- **Rejection Reason**: 语义不匹配是硬伤;现形态无此问题。
## Consequences
### Positive
- 结构与切片零迁移成本;core 隔离规则可静态扫描(ADR-0004)
- 棋盘形态留有 D1 期复议空间且复议成本已如实登记
### Negative
- 逐格控件形态在 D1 需要高表现力时可能仍需迁移 _draw(成本已登记:探针改造 + 命中测试自建 + push_input 端到端探针)
- CJK 字体为新增资产义务(发行体积 +)
### Risks
- D1 正式化时遗漏"探针与形态耦合"约束 → 缓解:本 ADR Decision 3 + ADR-0004 探针节双登记
- core 类被随手加 autoload 裸引用 → 缓解:ADR-0004 静态扫描(parse 期即炸的事实使该风险自暴露)
## GDD Requirements Addressed
| GDD System | Requirement | How This ADR Addresses It |
|------------|-------------|--------------------------|
| d2-data-config.md | Rule 7 零 UI 依赖;Rule 2 单点访问 | core 隔离规则 + autoload 清单固化(ConfigDB/RngService 定名) |
| game-concept.md | 视觉锚(绘制优先级栈、双通道) | 棋盘形态两案对比留档,D1 正式化按锚需求裁决 |
| b1-settlement.md | Core Rule 1 编排器按局生命周期 | 编排器非 autoload、组合根按局实例化 |
| docs/code-conventions.md | 领域命名 / 组合根纪律 | 目录结构与 Key Interfaces 转正 |
## Performance Implications
- **CPU**: 144 常驻控件逐格更新——切片实测无压力;_draw 迁移后更优(单 draw call)
- **Memory**: 可忽略
- **Load Time**: CJK 字体载入一次性成本
- **Network**: 不适用
## Migration Plan
切片即目标形态(结构零迁移);CJK 字体接入与 _draw 复议归 D1 正式化批次。
## Validation Criteria
- 冒烟 88/88 + 三探针在正式工程维持全绿
- 静态扫描:core/ 零 Control extends / 零 .tscn preload / 零 autoload 全局标识符裸引用(ADR-0004 域)
- 中文文案在导出包内显示正常(导出 checklist)
## Related Decisions
- ADR-0001(接线纪律)、ADR-0002(渲染器与窗口键)、ADR-0004(扫描域与探针)
- docs/architecture/architecture.md(Module Ownership / Required ADRs——本 ADR 落定后其 §Required ADRs 第 3 项闭合)
@@ -0,0 +1,132 @@
# ADR-0004: 测试载体与 CI(Test Carrier & CI)
## Status
Accepted(2026-09-12,solo 模式用户授权代决;迭代期可推翻重议)
## Date
2026-09-12
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Godot 4.7.2-stable |
| **Domain** | Core / Scripting(测试与工具链) |
| **Knowledge Risk** | LOW(--script/探针模式为 4.x 基线;godot-specialist 校验通过,含两条规则升格) |
| **References Consulted** | `docs/engine-reference/godot/`(四文件);godotengine/godot#6360(--script 模式不加载 autoload + 不注册全局标识符——官方立场) |
| **Post-Cutoff APIs Used** | None |
| **Verification Required** | None(切片 88/88 + 三探针已实证;CI 外部超时在正式 CI 接入时核对) |
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | ADR-0003(core 隔离规则是扫描域的输入) |
| **Enables** | 全部 GDD AC 的可执行化;正式 CI 接入 |
| **Blocks** | `/gate-check` 前置项之一(`/test-setup` 以本 ADR 为基线) |
| **Ordering Note** | gdUnit4 复议登记为 D2 OQ1 的关闭形态(推迟,非否决) |
## Context
### Problem Statement
D2 GDD 定下 `--headless --script` 冒烟载体(脚本 extends SceneTree、autoload 显式实例化),D2 OQ1 留下"gdUnit4 是否引入"待决;切片新增了场景级 UI 探针形态;项目级静态扫描域散落在各 GDD AC(AC-9/17/21/22/43 等)需要一处统一登记。
### Constraints
- headless 全套 < 30s 预算(technical-preferences)
- 致命类用例独立进程协议("调用 → 期望退出码" runner manifest,D2 AC-2b 起)
- 呈现层测试必须有承载(切片教训:已定稿 AC 全覆盖 core、呈现层零测试承载 → 缺陷 ①②④⑤ 漏网)
### Requirements
- 双模式边界明确:逻辑冒烟(无 autoload)vs 场景探针(完整项目引导)
- 静态扫描规则统一载体,覆盖 D2/A1–B4 各 AC 登记的扫描项
- CI 可接入且防挂死
## Decision
**双载体 + 统一静态扫描域;gdUnit4 推迟到正式 CI 期。**
**1. 逻辑冒烟(`--headless --script`)**:脚本 extends SceneTree 以获得 `quit(code)`;ConfigDB/RngService 由脚本显式实例化(引擎事实:--script 模式**不加载 autoload 且不注册全局标识符**——裸引用 = parse error 编译期即炸,GH #6360 官方确认)。Windows 用 console 版 exe 取 stdout。承载:D2/A1–B4 全部纯逻辑 AC + 致命类独立进程 manifest。
**2. 场景探针(headless 跑 .tscn 探针场景)**:以探针场景为运行目标(`godot --headless --path . res://smoke/<probe>.tscn`)走正常项目引导——autoload 在位、真实 SceneTree、`await process_frame` 有效。承载:呈现层场景级测试(切片三探针 ui_probe / ui_click_probe / ui_tune_probe 为先例)。
- **口径修正(specialist 校验)**:click_probe 走 `BoardView.click_cell` 是"真实翻译出口",**绕过 Viewport GUI 管线**(无命中测试/悬停/焦点)。表述定为"合成输入走真实翻译出口";真正的端到端输入测试 = `Input.parse_input_event()` / `Viewport.push_input` 带坐标——若 ADR-0003 的 _draw 迁移执行(Button 命中测试消失),push_input 探针从加分项变为**必需项**(登记入迁移工作项)。
**3. 项目级静态扫描域(统一登记,CI 与冒烟前置双跑)**:
| # | 规则 | 来源/动机 |
|---|---|---|
| S1 | 玩法代码禁 `randomize()`、自建 `RandomNumberGenerator` | D2 Rule 5(既有) |
| S2 | **禁全局随机函数 `randi()` / `randf()` / `randf_range()`**(全局 RNG 绕开 RngService——specialist 补登漏洞) | D2 Rule 5 补全 |
| S3 | core/ 禁 ConfigDB 外 data 表加载(`load(` / `preload(` / `ResourceLoader.load`;schema class_name 直引豁免) | D2 Rule 2(既有) |
| 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);发射者禁持有订阅者反向引用 | ADR-0001(防循环引用/生命周期延长) |
| S7 | B1 编排唯一性:B2/B3/B4 API 符号命中文件 ⊆ B1 文件集;A2/B3/B4 零相互直调 | B1 AC-43(既有) |
| S8 | 结算路径 `await` 零命中(B1 结算/编排文件集);字面量扫描 = warning 级 + 人工三要素复核(D2 AC-14 口径) | B1(既有) |
**4. CI 防挂死**:任何探针/冒烟调用旁必须配**外部 wall-clock 超时**(命令行 timeout)——引擎自身无死锁探测;探针内 30 帧停滞检测(click_probe 先例)是第一道、外部超时是最后防线。headless 全套 < 30s 预算不变。
**5. gdUnit4:推迟到正式 CI 期再议**(D2 OQ1 以"deferred"关闭)。它运行在项目模式(autoload 在位),与 SceneTree 冒烟机制正交,引入不废弃既有用例。
### Architecture Diagram
```
提交 → [静态扫描 S1–S8(秒级)] → [逻辑冒烟 --script(独立进程 manifest,<30s)]
→ [场景探针 ×N(headless + 外部超时)] → 全绿放行
```
### Key Interfaces
- 冒烟 runner manifest:条目 = "调用 → 期望退出码"(独立进程、游戏通道)——D2 AC-2b 协议延续
- 探针退出码:0 = PASS;非 0 = 失败类别;停滞 quit(2)
## Alternatives Considered
### Alternative 1: 立即引入 gdUnit4 并迁移全部冒烟用例
- **Pros**: 标准 assertions/参数化/报告;编辑器面板。
- **Cons**: 88 条用例迁移成本即刻发生;切片框架已验证且快(~3s);MVP 期无 CI 基建需求。
- **Rejection Reason**: 收益在 CI 期才兑现——推迟(非否决),D2 OQ1 关闭形态。
### Alternative 2: 仅保留逻辑冒烟、UI 靠人工
- **Cons**: 切片缺陷 ①②④⑤ 全部漏过既有 AC 的教训直接否定——呈现层无承载 = 呈现层缺陷零防线。
- **Rejection Reason**: 呈现层契约测试是 code-conventions 硬性要求。
## Consequences
### Positive
- 双模式边界以引擎事实固化(parse 期炸 vs 完整引导),不再靠约定俗成
- S2/S5/S6 三条 specialist 补登规则堵住静默翻车路径(全局随机绕开、裸引用编译炸、lambda 成环)
- 呈现层探针形态沉淀为项目标配(code-conventions 承载)
### Negative
- 静态扫描规则 8 条的维护成本(新规则须挂真实反面案例——code-conventions 纪律)
- "真实 UI 路径"表述收窄后,端到端输入覆盖存在已知缺口(_draw 迁移前可接受)
### Risks
- 未被停滞检测覆盖的死锁挂死 CI → 缓解:外部 wall-clock 超时为硬性要求
- 扫描规则漏报(启发式)→ 缓解:行为级硬证据并行(可回放断言、headless 实跑合成判定——D2 AC-9/10 模式)
## GDD Requirements Addressed
| GDD System | Requirement | How This ADR Addresses It |
|------------|-------------|--------------------------|
| d2-data-config.md | Rule 6 校验双通道;AC-2b 独立进程协议;OQ1 gdUnit4 | 冒烟框架定稿;manifest 延续;OQ1 关闭为 deferred |
| a1–b1 各 GDD | 各自静态扫描 AC(AC-9/17/18/19/21/22/43 模式) | S1–S8 统一登记为项目级域,各 AC 引用本 ADR |
| docs/code-conventions.md | 场景探针标配 / 提交前全量 | 双载体 + CI 链路承载 |
| b1-settlement.md | AC-43 编排唯一性单向扫描 | S7 载体 |
## Performance Implications
- **CPU**: 全套冒烟 ~3s(切片实测),预算 <30s 富余
- **Memory**: headless dummy server 极低
- **Load Time**: 探针场景引导一次性
- **Network**: 不适用
## Migration Plan
切片 smoke/ 目录即载体(零迁移);正式 CI 接入时补外部超时包装与 S1–S8 脚本化;`/test-setup` 以本 ADR 为基线脚手架。
## Validation Criteria
- 冒烟 + 探针全绿为提交门(code-conventions"提交前全量")
- 扫描域脚本化后:新增违规案例必须先补扫描规则再修码(缺陷-规范对照表纪律)
- `<30s` 预算在 CI 环境复核
## Related Decisions
- ADR-0001(S6 规则来源)、ADR-0003(S4/S5 规则来源、探针形态耦合)
- docs/code-conventions.md(缺陷-规范对照表)、各 GDD AC 前言(规范源)
@@ -0,0 +1,104 @@
# ADR-0005: 存档契约与版本化(Save Contract & Schema Versioning)
## Status
Accepted(2026-09-12,solo 模式用户授权代决;轻量契约 ADR——实现归 D3 Alpha,本 ADR 只锁契约使 Foundation 层 TR 覆盖闭合)
## Date
2026-09-12
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Godot 4.7.2-stable |
| **Domain** | Core(数据/序列化契约) |
| **Knowledge Risk** | LOW(契约层; FileAccess/JSON 基线稳定) |
| **References Consulted** | `docs/engine-reference/godot/`(四文件);D2 GDD Rule 8 / EC9 / RNG 生命周期 |
| **Post-Cutoff APIs Used** | None |
| **Verification Required** | None(D3 实现期按契约验证) |
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | None |
| **Enables** | D3 存档序列化(Alpha)——契约先行,实现期免重议 |
| **Blocks** | None |
| **Ordering Note** | 清零 Foundation 层 TR 覆盖缺口(requirements-traceability.md 的最后两条 pending) |
## Context
### Problem Statement
Technical Setup → Pre-Production 门要求"Foundation 层需求 ADR 覆盖零缺口"。63 条 TR 基线中 Foundation 层尚有两条无 ADR 承载:TR-d2-010(SCHEMA_VERSION 版本化)与 TR-gc-002(RNG 快照存续契约)。两者的契约在 D2 GDD 已定但散落,未 ADR 化——本 ADR 收拢,D3 Alpha 实现时照契约执行。
### Constraints
- MVP 无元进度存档(C4/D3 归 Alpha)——本 ADR 不引入任何 MVP 实现
- RNG 快照语义已被 D2 GDD(RNG 生命周期)与 B1 GDD(回放切点)锁定——本 ADR 只做契约归集,不改语义
### Requirements
- 版本不匹配拒绝读取(高版本拒读,D2 EC9)
- 续玩/复盘可从快照精确恢复随机序列
- 快照切点与在飞流实例的互斥有明确归属
## Decision
**1. 版本化契约**:`SCHEMA_VERSION` 常量归 D2(既定);存档文件携带该版本号;**比较逻辑归 D3**(D2 EC9 既有分工)——存档版本 > 当前 SCHEMA_VERSION → 拒绝读取 + 明确错误信息;低版本存档的迁移策略归 D3 Alpha 设计时定(候选:拒读 / 迁移器——届时另议,非本 ADR 范围)。
**2. RNG 快照契约(从 D2/B1 GDD 收拢为 ADR 登记)**:
- 快照内容 = `master_seed` + 各封闭流 scope 计数器 + 意图序列 + 窗口选择序列
- **切点规则**:快照切出必须发生在 scope 事件边界之外(无在飞 `open_stream` 实例时);运行期 scope 事件边界 = board install(A1 取号 / B4 取号)——B1 编排保证无在飞实例跨界(消费侧遵守义务,B1 GDD 回放切点条款)
- 违反后果:续玩/复盘与实况分叉(D2 AC-15 语义失效)——该前提由 D3 消费侧遵守,B1 侧编排义务已在位
**3. 存档内容归属预告(D3 Alpha 实现的输入清单,非本 ADR 决策)**:冒险结构(C2 域)、关卡进度(C1 域)、HP/库存(B1/B4 关卡域)、钥石/天赋(C4 MetaTable)、RAP 不持久(战斗域清零)。D3 GDD 立项时按此清单展开。
### Architecture Diagram
(不适用——契约 ADR;数据流见 architecture.md §Data Flow 3)
### Key Interfaces
- D3 → D2:读取 `SCHEMA_VERSION`(比较方向:存档版本 ≤ 当前才可读)
- D3 → RngService:只读计数器接口(D2 已暴露)+ `set_master_seed`(仅 C2 调用——D3 只读快照、恢复时经 C2 重放接线)
## Alternatives Considered
### Alternative 1: 不立 ADR,依赖 D2/D3 GDD 散落条款
- **Cons**: gate 的 Foundation 零缺口检查失败;契约散落三处(D2 EC9 / D2 RNG 生命周期 / B1 回放切点),D3 实现时需跨三文档拼装。
- **Rejection Reason**: 收拢成本一次会话内,收益是门检查全绿 + D3 立项输入就绪。
### Alternative 2: 现在就设计存档格式(JSON vs 二进制 vs Resource)
- **Cons**: C4/D3 未设计,过早——MVP 无存档需求牵引。
- **Rejection Reason**: 违背"schema 先行、数值回填"的项目节奏;格式决策留给 D3 GDD。
## Consequences
### Positive
- Foundation 层 TR 覆盖零缺口(门检查全绿)
- D3 立项时的硬输入就绪(版本比较方向、快照内容、切点规则、内容归属清单)
### Negative
- 无(纯收拢,零新约束)
### Risks
- D3 Alpha 实现时偏离切点规则 → 缓解:本 ADR + D2 AC-15 / B1 AC-34 双侧断言承载
## GDD Requirements Addressed
| GDD System | Requirement | How This ADR Addresses It |
|------------|-------------|--------------------------|
| d2-data-config.md | Rule 8 版本化;EC9 高版本拒读;RNG 快照生命周期 | 契约收拢 + 版本比较归属(D3)成文 |
| b1-settlement.md | 回放切点契约(快照语义 = seed + 计数器 + 两类输入序列) | 切点规则 ADR 化(无在飞实例时切出,scope 边界 = board install) |
| game-concept.md | 存档/冒险/关卡术语层级(C4/D3 Alpha 范围) | 存档内容归属预告清单 |
## Performance Implications
- **CPU / Memory / Load Time / Network**: 无(MVP 零实现;Alpha 期存档体积预期 < 1MB 量级)
## Migration Plan
无迁移(MVP 零实现)。D3 GDD 立项(Alpha 期)以本 ADR 为硬输入。
## Validation Criteria
- D3 实现后:同快照恢复 → 随机序列逐位一致(D2 AC-15 / B1 AC-34 形态)
- 版本不匹配路径拒绝读取且错误信息含两侧版本号
- tests/integration/ 收录存档往返测试(D3 落地时)
## Related Decisions
- ADR-0001(回放输入序列的载体)、ADR-0004(集成测试载体)
- docs/architecture/requirements-traceability.md(TR-d2-010 / TR-gc-002 覆盖闭合)
@@ -0,0 +1,141 @@
# ADR-0006: 战斗棋盘 Node2D 渲染迁移(Board Node2D Rendering Migration)
## Status
Accepted(2026-09-23 用户拍板方向;本 ADR 落文。Phase A 棋盘实施另批执行;B 区舞台迁移为后续批,本 ADR 只定分工不排期)
部分取代 ADR-0003 Decision 3(形态条款定向 Node2D,「禁盲目迁移」解除;autoload 清单 / 类形态 / CJK 字体前置等其余条款不变)
## Date
2026-09-23
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Godot 4.7.2-stable |
| **Domain** | UI / 棋盘渲染 |
| **Knowledge Risk** | MEDIUM(SubViewport 内 theme 传播与像素缩放纹路两项待实施批实证;其余为已验证引擎面) |
| **References Consulted** | `docs/engine-reference/godot/`;ADR-0003 Decision 3 形态对比留档;art-bible §8.2 画布暂定档 |
| **Post-Cutoff APIs Used** | None |
| **Verification Required** | SubViewport 内 CJK 字体显式注入核对(实施批);分辨率两档装板校验(实施批) |
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | ADR-0001(组合根接线纪律)、ADR-0002(渲染器与窗口键)、ADR-0003(场景结构与渲染形态——本 ADR 修订其 Decision 3) |
| **Enables** | 表现层升级(B 区站桩 / 意图三档动画 / P3 演出需要 Node2D 载体);美术终稿资产 1:1 换入的槽位体系 |
| **Blocks** | 无(B 区舞台迁移为本 ADR 分工内的后续批,不阻塞他批) |
| **Amends** | ADR-0003 Decision 3(逐格控件形态维持条款)与 Alternative 2(逐格 Node2D 否决——否决理由「无必要」因表现层升级需求不再成立,节点开销论据维持有效) |
## Context
### Problem Statement
ADR-0003 Decision 3 将棋盘渲染冻结为「GridContainer + 逐格常驻控件」,并登记 _draw/自绘迁移为 D1 正式化候选、迁移前禁盲目动手(探针耦合是登记成本)。2026-09-23 用户拍板:表现层走 Node2D 重构,**只迁战斗棋盘**,开始菜单(ui_assembler build_start_overlay)与各覆盖层保持 Control——分工不是替换。原「禁盲目迁移」条款与现行裁决冲突,需要新 ADR 定向解禁并落分工边界。
### Constraints
- 战斗屏四区布局与分区尺寸是 HUD框架 结构冻结项——本迁移只换渲染技术,四区结构、双舞台常驻、尺寸表零改动(战斗屏进度条退场与四区→三区重排由停点前移批按文档修订流程另行落地,与本批解耦)
- 组合根纪律(ADR-0001):核心写调用唯一落点不变;BoardView 信号外发 + 只读注入模式不变
- 性能预算(HUD框架 引用值):同屏 draw calls < 200;进入战斗屏 500ms 内首格可点
- 美术终稿资产全部缺位(样张批未跑)——迁移必须以程序化占位服役为前提,不新增素材前置
- core/ 零 UI 依赖(D2 Rule 7)——本迁移全部发生在 presentation/,逻辑层零接触
### Requirements
- B 区战斗画面(站桩立绘、意图三档动画、受击/死亡演出)的后续迁移不被本批堵死——骨架同构可复制
- 分辨率两档(1280×800 / 1920×1080,2026-09-22 定案)与「格边长按分辨率档位计算」口径(2026-09-18 拍板,推翻两档定值冻结)继续成立
- 三探针(ui_probe / ui_click_probe / ui_layout_probe)与冒烟全量基线不破
## Decision
**1. D 区棋盘本体迁 Node2D 渲染,BoardView 类名与公共面契约保持不变。**
BoardView(`presentation/views/board_view.gd`,现 extends Control + 内部 GridContainer)重构为:Control 壳参与屏幕栅格布局不变,壳内经 **SubViewportContainer + SubViewport + Node2D 世界**(BoardWorld)渲染棋盘。对外契约逐项保持:
| 契约面 | 消费方 | 保持方式 |
|---|---|---|
| 类名 `BoardView` / extends Control | ui_assembler 组装、屏幕栅格布局 | 不变 |
| 信号 `action_requested` / `hover_forbidden` / `chord_attempted` | 组合根动作通道 | 不变 |
| `click_cell(cell, button)` 公共入口 | ui_click_probe 直调路径(ui_click_probe.gd:257–277) | 不变 |
| `_cells` 字典(Vector2i → 格单元) | ui_probe `_cells.is_empty()/.size()` 断言(ui_probe.gd:73/187)、ui_click_probe.gd:97 | 键面不变,值 Button → 格单元 Node2D |
| `board()` / `set_desaturated()` / `set_identity_resolver()` / `set_variant_mask_resolver()` / `set_cell_area()` / `rebuild()` / `refresh()` | 组合根与探针 | 不变 |
| `cell_size` 只读属性 | ui_layout_probe `get("cell_size")`(现返回 null——`_cell` 为私有名) | 增补只读属性,顺手修正 |
**2. 渲染技术分工表(本 ADR 核心裁决——「三块模型」)。**
战斗屏渲染技术按内容形态切分,与屏幕布局正交。**战斗屏目标形态 = B/C/D 三区**(2026-09-23 用户裁定:战斗画面不再保留关卡进度条——战斗结束后返回关卡画面,节点链进度只留关卡画面):
| 内容 | 渲染技术 | 批次 |
|---|---|---|
| D 区棋盘本体(格面/数字/旗/徽章/变异纹带/高亮/微光) | **Node2D**(本 ADR) | Phase A(本批立项) |
| B 区战斗舞台(主角/敌人站桩、意图三档动画、受击/死亡演出、FX) | **Node2D** | Phase B(后续批,本 ADR 只定分工) |
| A 区关卡进度条 | Control | **战斗屏退场且代码退役**(裁定见下;退场落地随停点前移批——流程未倒置前 A 区仍是战斗中唯一节点链可视面,先删致盲,本批不动其现状) |
| C 区角色信息(数据框/状态槽/立绘) | Control | 不动 |
| D 区 AP 条、按钮组、toast 槽 | Control | 不动 |
| B 区结算明细带、回合阶段指示、战斗日志 | Control(文本件) | 不动(Phase B 时留 B 区 Control 半区) |
| 全部弹层(选路/节点/结算/图例/营地/开始菜单) | Control | 不动(2026-09-23 用户拍板) |
**A 区退场形态(2026-09-23 追问裁定)**:LevelStripView **退役,不迁移**;关卡画面**不重新实现**——由 RouteOverlay(整幅节点图选路层,节点态色板语义与压缩条同族、候选按钮与放弃入口在役)升级为常驻页面承接。压缩条唯一存在理由(战斗屏无整图时的常显替代面)随零进度裁定消亡,无独有资产流失;唯一继承面 = 逻辑层节点数据面(runner 只读出口零改动)。退役删除面清单见迁移方案 §1。
**3. 命中测试 = 板级公式换算单点,非逐格 Area2D。**
鼠标输入经 BoardView 壳层 `_gui_input` 接收(Control 已有的矩形命中),格坐标 = `floor(local_pos / 格边长)` 单点公式换算——悬停/点选/双击 chord/长按全部复用现有逻辑入口。不为 144 格建 Area2D(节点开销无必要,Alternative 2 论据沿用);ADR-0003 登记的「自建命中测试」成本由此收敛为一个公式函数。无障碍面缺失为接受后果(沿 ADR-0003 Decision 3 已登记口径,键盘寻格体系延后)。
**4. 素材槽位按圣经 §8.2 暂定档预留,程序化占位继续服役。**
格单元内部为固定叠层序的精灵槽位(砖面 < 变异纹带 < 数字/徽章 < 旗 < 焦点高亮),本批全部槽位由程序化绘制填充(现行占位等价平移);终稿纹理(格面 32×32 暂定档)到位后 1:1 换入,零结构返工。**美术缺口不阻塞本迁移**——样张批终值(格边长×格源占比摩擦,v1.9 待决注记①路线)只换 `set_cell_area` 单点数值,不动架构。
**5. SubViewport 模式为两舞台统一骨架,Phase B 复制不返工。**
Phase B 届时将 BattleStageView 同构改造(PanelContainer → Control 壳 + SubViewport 舞台 + 结算明细带 Control 半区)。本批在 ui_assembler 组装侧不引入「单舞台」写死假设,SubViewport 内 theme/字体注入做成可复用挂点(两舞台共用)。
## Alternatives Considered
### Alternative 1: 维持逐格控件形态(ADR-0003 现行)
- **Cons**: 表现层升级(站桩动画/演出/FX)在 Control 树内无载体,最终仍需二次迁移;两次改造总成本高于一次定向。
- **Rejection Reason**: 2026-09-23 用户已定向 Node2D;本 ADR 职责是把方向落为工程边界。
### Alternative 2: 直挂 Node2D(Control 壳内手挂 Node2D,无 SubViewport)
- **Cons**: 屏幕坐标↔棋盘坐标换算、缩放纪律、两档分辨率适配全部自建;布局联动手工维护。
- **Rejection Reason**: SubViewportContainer 是 Control——容器栅格布局引擎原生参与,鼠标坐标引擎换算,内部分辨率固定使像素缩放纪律有单点可管。
### Alternative 3: 逐格 Area2D 承载命中
- **Cons**: 144 个 Area2D 节点开销与信号接线量;命中语义(相邻约束/chord/长按)仍要自建。
- **Rejection Reason**: 板级公式换算单点已覆盖全部交互语义,物理命中是纯开销。
### Alternative 4: TileMapLayer 承载棋盘
- **Rejection Reason**: ADR-0003 已否(瓦片语义与变体标记/数字/旗多层叠加不匹配),维持。
### Alternative 5: B 区与棋盘同批迁移
- **Cons**: B 区站桩形态绑美术样张批(剪影/动画方案未冻结),混批互卡。
- **Rejection Reason**: 2026-09-23 用户拍板分批——先只迁棋盘,战斗画面 Node 后续完成。
## Consequences
### Positive
- 表现层升级获得渲染载体;棋盘先行验证 SubViewport 骨架,B 区复制模式即可
- BoardView 公共面契约保持——三探针与组合根零改造(实证耦合面仅 `_cells` 键面/`click_cell`/`cell_size`,全部保留)
- 素材终稿 1:1 换入,样张批与渲染批解耦,互不阻塞
- 逻辑层(core/)零接触——MC/模拟试玩/verifier 读数面零影响
### Negative
- Node2D 世界内 theme 不自动继承主场景 Control 主题——CJK 字体与 ui_kit 样式需显式注入(实施批义务,ADR-0003 Decision 4 关联)
- 键盘寻格/无障碍面缺失口径延续(已登记接受)
- 非整数缩放的像素纹路风险(45/36px 锚点 × 32px 格源非整倍)——沿 §8.2 路线归样张批定终值,架构只保证 `set_cell_area` 单点可调
### Risks
- SubViewport 渲染目标在低端机的显存/带宽开销 → 缓解:棋盘区逻辑分辨率小(≤ 12×12×36px 量级),实施批实测帧预算
- 输入路径改 `_gui_input` 后现探针全走 `click_cell` 直调,公式换算无自动化覆盖 → 缓解:实施批增补 push_input 端到端输入探针(ADR-0003 已登记项,本批转义务)
## Migration Plan
Phase A(棋盘,实施另批):BoardView 内部重构 + 探针增补 + 冒烟全量回归 + 文档随批(HUD框架渲染注记/battle.md 实现注记/architecture.md 模块归属行)。Phase B(B 区舞台,后续批):同构复制 + 站桩精灵迁移 + 意图动画承载;开工前置 = 样张批冻结剪影与动画方案。战斗屏进度条退场的 GDD 修订面(HUD框架 四区表、关卡节点地图 节点链常显条款、battle.md 像素布局表与 28px 纵向空间再分配)随停点前移批(关卡画面成为常驻枢纽)落地,不随本批。工程细节见同目录 `board-node2d-migration-plan.md`。
## Validation Criteria
- 冒烟全量绿(现行基线不破);三探针(ui / ui_click / ui_layout)绿
- 新增 push_input 端到端输入探针绿(悬停/左键/右键/chord 各一case)
- 功能等价清单:数字/旗/徽章/危险红/变异纹带/呼吸微光/悬停/路径预览/灰化(set_desaturated)/出生展开区 R₀ 豁免,逐项可点可验
- 性能预算:同屏 draw calls < 200;进入战斗屏 500ms 内首格可点
- 分辨率两档(1280×800 / 1920×1080)装板校验通过(12×12 与 10×10 板各验)
## Related Decisions
- ADR-0003(本 ADR 部分取代其 Decision 3;Alternative 2 否决理由更新)
- ADR-0001(组合根纪律——BoardView 信号外发模式沿袭)
- art-bible §8.2(画布暂定档与缩放纪律)、HUD框架(四区结构冻结与性能预算引用值)、battle.md(格尺寸公式锚点)
+295
View File
@@ -0,0 +1,295 @@
# Dungeon Sweeper — Master Architecture
## Document Status
- Version: 1.3
- Last Updated: 2026-09-26(战斗舞台 Node2D 迁移 Phase B:D1 层表 BattleStageView 形态注记——引 ADR-0006;Phase A 注记 2026-09-23)
- Engine: Godot 4.7.2-stable(GDScript 单语言;2026-09-07 锁定)
- Review Mode: solo(`production/review-mode.txt`,2026-09-12 用户拍板——TD/LP 门禁按 solo 跳过,用户授权架构产出自主推进、迭代期回调)
- GDDs Covered: game-concept · game-pillars · systems-index · d2-data-config · a3-action-economy · a4-terrain-variation · b3-status-effect · a1-board-generation · a2-minesweeper-rules · b2-enemy-system · b4-reward-item-economy · b1-settlement(12 份;A5/C1–C4/B5/D3–D6 未设计,按 systems-index 依赖分层预留)
- ADRs Referenced: 无(0 份现存)——本文档 Phase 5/6 列出的实现期裁决项即为首批 ADR 候选
- Implementation Evidence: 垂直切片 `game/`(2026-09-10 起,冒烟 88/88、三 UI 探针 PASS)——本文档的分层/所有权/数据流均经切片实现验证,非纯纸面设计
- Technical Director Sign-Off: 2026-09-12 — APPROVED WITH CONDITIONS(条件:Required ADRs 于正式工程骨架搭建前定稿;`.claude/docs/director-gates.md` 缺失已登记技术债)
- Lead Programmer Feasibility: SKIPPED — Solo mode
---
## Engine Knowledge Gap Summary
- **风险等级:MEDIUM**(2026-09-12 `/setup-engine refresh` 后由 HIGH 降级)。4.5→4.7 官方迁移指南已逐条核实并入 `docs/engine-reference/godot/breaking-changes.md` 与 `deprecated-apis.md`;**结论:无一条破坏 GDD 已锁架构契约**(autoload 体系 / .tres 单一事实源 / Callable 信号 / int64 二补数 / headless SceneTree 协议均不受影响)。
- 写码纪律不变:不确定的 API 一律查 `docs.godotengine.org/en/4.7/` 核实后再用;4.8(dev 阶段)稳定版发布后跑 `/setup-engine refresh`。
- 三类必须入编码纪律的引擎事实(详见 `docs/engine-reference/godot/current-best-practices.md`):
1. **GDScript 4.7 两条语义变化**:打包数组元素赋值不再触发属性 setter;类型化返回的方法覆盖必须显式 `return`。
2. **新项目默认值已变**:stretch mode `canvas_items` / aspect `expand`(4.7);Windows 渲染驱动 D3D12(4.6,仅 Forward+ 线)——创建 project.godot 后逐项核对。
3. `Resource.duplicate(true)` 深复制不再覆盖外部子资源——D2 表资源复制语义注意。
- 残余风险敞口:modules/ 分域参考未建(按实现期需要建,非阻塞);UI/渲染域(D1/B5)为 MEDIUM 风险集中地,随 ADR-0002/0003 裁决时对照引擎文档核实。
---
## System Layer Map
20 系统按 systems-index 依赖分层表(systems-design 门禁已评审)映射到标准五层。切片实现已验证该分层(core/ 纯逻辑零 UI 依赖 headless 可跑、presentation/ 组合根模式)。
```
┌────────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ D1 HUD 与界面框架 (MVP) · B5 战斗表现 (切片后) │
│ D4 音频 (Alpha) · D5 教学引导 (Alpha) │
├────────────────────────────────────────────────────────────┤
│ FEATURE LAYER │
│ B1 战斗结算编排 (MVP) · C1 关卡节点地图 (切片已含 lite) │
│ C2 冒险结构 · C3 难度曲线 · C4 元进度 (Alpha) │
├────────────────────────────────────────────────────────────┤
│ CORE LAYER │
│ A1 扫雷棋盘生成 · A2 排雷玩法规则 · B2 敌人系统 │
│ B4 地图战斗资源 · A5 扫雷魔法道具 (垂直切片) │
├────────────────────────────────────────────────────────────┤
│ FOUNDATION LAYER │
│ D2 数据配置与数值表 · A3 行动经济 · A4 地形变异 │
│ B3 增益/状态系统 · D3 存档序列化 (Alpha) · D6 设置/本地化│
├────────────────────────────────────────────────────────────┤
│ PLATFORM LAYER │
│ Godot 4.7.2 引擎 API 面(GDScript / Resource / │
│ SceneTree / Control / InputEvent)——无游戏系统 │
└────────────────────────────────────────────────────────────┘
```
| 系统 | 层 | 里程碑 | headless 约束 | 引擎风险域 |
|---|---|---|---|---|
| D2 数据配置 | Foundation | MVP | ✅ 完整可跑 | LOW(Resource/.tres 基线稳定) |
| A3 行动经济 | Foundation | MVP | ✅ 完整可跑 | LOW(纯逻辑) |
| A4 地形变异 | Foundation | MVP | ✅ 完整可跑 | LOW(纯逻辑) |
| B3 状态系统 | Foundation | MVP | ✅ 完整可跑 | LOW(RefCounted 纯逻辑) |
| A1 棋盘生成 | Core | MVP | ✅ 完整可跑 | LOW(纯逻辑;性能姿态归 ADR-0005) |
| A2 排雷规则 | Core | MVP | ✅ 完整可跑 | LOW(零表读取零 UI) |
| B2 敌人系统 | Core | MVP | ✅ 完整可跑 | LOW(纯逻辑) |
| B4 地图战斗资源 | Core | MVP | ✅ 完整可跑 | LOW(纯逻辑) |
| B1 战斗结算编排 | Feature | MVP | ✅ 完整可跑(禁 await) | LOW(纯逻辑;事件载体归 ADR-0001) |
| C1 关卡节点地图 | Feature | 垂直切片(lite 已入切片) | ✅(切片 level_runner 先例) | LOW |
| C2 冒险结构 | Feature | 垂直切片 | ✅ | LOW |
| C3 难度曲线 | Feature | 垂直切片 | ✅ | LOW |
| C4 元进度 | Feature | Alpha | ✅ | LOW |
| D1 HUD | Presentation | MVP | ❌(UI 层——场景级探针测试承载) | **MEDIUM**(Control/Input/文本域) |
| B5 战斗表现 | Presentation | 垂直切片 | ❌ | **MEDIUM**(Animation/Tween/渲染) |
| D4 音频 / D5 教学 | Presentation | Alpha | 部分 | LOW-MEDIUM |
| D3 存档序列化 | Foundation | Alpha | ✅ | LOW |
| D6 设置/本地化 | Foundation | 完整愿景 | ✅ | LOW |
**MVP 关键路径**(切片已验证):D2 → A3/A4/B3 → A1 → A2 → B2/B4 → B1 → D1-lite。
**依赖分层与唯一回边**:A2 只外发三事件(cell_revealed / flag_judged / round_end),B4/B1 订阅后回调——A2 不依赖任何下游(TD-SYSTEM-BOUNDARY 裁决,全项目唯一允许回边)。
---
## Module Ownership
> 命名遵循 `docs/code-conventions.md`:领域命名、禁 ID 前缀、头注溯源行(实现 GDD:design/gdd/…)。切片已按此规范完成领域命名批。
### Foundation 层
| 模块 | Owns(独占) | Exposes | Consumes | 引擎 API |
|---|---|---|---|---|
| **ConfigDB**(autoload,D2) | 全部玩法数值表(`data/tables/*.tres` + `data/schemas/*.gd`);SCHEMA_VERSION;加载/校验/override/诊断状态机 | 类型化属性读取;`load_tables(root, collect_only)`;`resolve()`(R1 floor);debug override;`set_reload_gate()`;诊断收集器 | 无(Foundation 顶点) | Resource/.tres 加载、autoload 生命周期——LOW |
| **RngService**(autoload,D2) | master_seed、各流 scope 计数器(封闭流:board_gen / reward_roll;增流走 P1 评审) | `set_master_seed()`(唯一调用方 C2);`open_stream(stream)` 有状态门面;`derive_at(stream, scope)` 纯函数;只读计数器 | ConfigDB(无) | 无——纯逻辑 |
| **action_economy**(A3) | AP/RAP 账本、last_round_end、轮次相位机 {Idle, Active, Waiting}、结束仲裁 | 8 API 全布尔面:notify_round_start / request_dig / request_flag_place / request_flag_pull / request_stop / request_refresh / notify_settlement_done / notify_combat_end;ap/rap/phase 只读 | ConfigDB(ActionEconomyTable);B1 回发 next_round_mode/battle_over | 无 |
| **variation 定义**(A4) | VariationTable schema(现行空清单,合法态)与 F1 候选池门控谓词 | `variant_pool_gate(level_index, board_biome)` | ConfigDB(VariationTable) | 无 |
| **status_container**(B3) | 状态实例容器(战斗域生命周期、跨轮跨图延续) | 5 API:notify_battle_start / apply_effect / query_active / decay_round / notify_battle_end | ConfigDB(StatusEffectTable) | 无(RefCounted) |
| **save_system**(D3,Alpha) | 存档格式与版本比较 | (未设计——契约锚:SCHEMA_VERSION + master_seed + 各流 scope 计数器快照,须在无在飞 open_stream 实例时切出) | ConfigDB、RngService 只读计数器 | FileAccess——LOW |
| **settings/localization**(D6,愿景) | 设置项、翻译数据 | (未设计) | ConfigDB | 无 |
### Core 层
| 模块 | Owns | Exposes | Consumes | 引擎 API |
|---|---|---|---|---|
| **board_generator**(A1) | 生成算法(五阶段管线)、BoardGenTable 回填、生成期指标集、合法性谓词(clearable / R₀≠S / min_safe)、可推理率度量(metric_version) | `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)`(唯一提交入口);try_dig / try_flag_place / try_flag_pull / try_move(便捷形);`compute_move_path(cell)` 只读;`retire_board()`;三源拒绝(board_face / resource_face / phase)+ sub_reason 封闭枚举 | 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 / set_pending_action / pending_action / basic_streak / commit_action / tick_cooldowns / apply_damage / heal / default_target / ambush_grant_plan / despawn_all(2026-09-16 敌人技能池批:query_beat/query_attack/advance 随行为环退役——architecture.md 旧口径残留归 API 面 ADR 批整表复核) | ConfigDB(EnemyTable/SkillTable/HeroTable) | 无 |
| **reward_economy**(B4) | 格面资源身份映射(install 期预分配、Ready 只读、换图映射消亡)、极性路由字典面((类型, 极性)→ 字典行查询;路由执行归 B1 授予段)、F6 当量供给投影 | 封闭 API:roll_board / query_identity / query_full_snapshot / grant / grant_clear / retire_mapping / polarity_row / equivalence_supply / phase / weighted_pick;另有 is_mapped 静默布尔投影(批量工具用) | ConfigDB(RewardTable);reward_roll 流(scope = 每次 install 批) | 无 |
| **helper_items**(A5,垂直切片) | HelperItemTable、探测显影语义 | (未设计——桥接位:A2 查询接口 + A4 counter_item_ids) | A2 / A4 / B4 | 无 |
### Feature 层
| 模块 | Owns | Exposes | Consumes | 引擎 API |
|---|---|---|---|---|
| **battle_orchestrator**(B1) | 段序骨架(七段结构冻结)、轮 facts 缓冲、可回放步骤流、[明细行协议](../../design/gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)(15 类行 + 字段分层)、条款③⑤闭合对账、**战斗域全部跨系统调用权** | `begin_battle(encounter)`;窗口/刷新意图提交面(扮演 D1);`level_clear()`;步骤流(输出契约) | B2/B3/B4 全调用面(**唯一调用方**);A3 request_refresh 代理 + 回发;A2 install/retire + 三事件订阅;A1 generate(attempt 非 scope 事件);D2 SettlementTable + set_reload_gate | 无(零随机、禁 await——切片 88 冒烟实证可回放) |
| **level_runner**(C1-lite,切片已建) | 关卡域生命周期(节点链推进、共享 B4 + HP 携带、关卡末 level_clear) | 节点推进接口;LevelTable(`level.tres`——节点 enum {battle,reward,rest,boss}、末节点必须 Boss、ConfigDB 跨表校验⑧) | B1 begin_battle、B4 grant_node_active、ConfigDB LevelTable | 无 |
| **run_structure**(C2) | 冒险/关卡序列、失败重开、**master_seed 唯一接线方** | (未设计) | RngService.set_master_seed、C3/D2 | 无 |
| **difficulty**(C3) | DifficultyTable 回填、全部曲线数值调参所有权、战斗期望表(切片 monte_carlo 为工具原型) | 调参输出(经 D2 R1 解析/编排器传递,禁反向调用生成) | ConfigDB、B1 对账输出(调参约束清单)、A1 报告 C | 无 |
| **meta_progression**(C4,Alpha) | MetaTable、钥石/天赋 | (未设计) | D3、C2、ConfigDB | 无 |
### Presentation 层
| 模块 | Owns | Exposes | Consumes | 引擎 API |
|---|---|---|---|---|
| **D1 HUD**(切片 lite 已拆 9 单元:BoardView / HUDView / HudPresenter / BattleLogView / ItemWindowOverlay / NodeOverlay / LevelStripView / TunePanel / UiKit + 组合根 main.gd) | 全部 Control/CanvasLayer(例外:BoardView 棋盘本体与 BattleStageView 站桩本体 = Control 壳 + SubViewportContainer + SubViewport + Node2D 世界〔BoardWorld/CellUnit;StageWorld/StandeeBody——B 区 slot_row 伴随件留壳层〕,探针契约面不变——ADR-0006 棋盘 Node2D 迁移批 Phase A 2026-09-23 / 战斗舞台 Phase B 2026-09-26)、输入→意图翻译(**唯一提交路径 = settle_batch,不直调 A3**)、呈现义务台账(A2×6 / B2×4 / B3×4 / B4×5 / B1×3) | 视图单元接口(信号外发、setter 只读注入) | 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 图片参数两轮变化) |
| **battle_presentation**(B5,垂直切片后) | 结算动画 / 伤害数字 / 战斗舞台 | (未设计——前置"战斗表现美术测试"锁双舞台陷阱) | 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 各教学条目) | 各上游 | 无 |
**依赖单向性**:上层可消费下层,禁止反向(C3 禁调用 A1 生成、D1 不直调 A3、A2/B3/B4 零相互直调)。全项目唯一回边 = A2 事件订阅面。
---
## Data Flow
### 1. 帧更新路径(2D 回合制:无逐帧玩法逻辑)
```
Input(鼠标)→ D1 输入翻译 → 意图对象 → A2.settle_batch(唯一提交入口)
→ A3.try_spend(原子扣减)→ A2 棋盘效果(挖/连锁/旗/移动)→ 三事件外发
→ [棋盘渲染:D1 BoardView 按事件增量重绘;无事件帧 = 静态 + 动效]
→ 轮次结束 → A3 Waiting → B1 结算编排(段序七段)→ 明细步骤流 → D1 结算屏逐行呈现
```
- 回合制下每帧只做:输入采样、呈现刷新、动效推进——**玩法状态只在意图事务内变更**(单意图帧输入收集:stop 点击终结当帧)。
- 无跨线程玩法数据流(Godot 主线程假设,D2 EC13);A1 生成若引入 WorkerThread 须走 ADR-0005 裁决(join 先于任何快照切出)。
### 2. 事件/信号路径(系统间通信,无紧耦合)
```
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 / stock / advance...──▶ B3/B2/B4(唯一调用方)
B1 ──install_board / retire_board──▶ A2 B1 ──generate(L,D)──▶ A1
D1 ◀──呈现数据源:A2 事件流 / B1 步骤流 / A3 ap-rap / ConfigDB display_name
```
- **载体裁决待 ADR-0001**:切片事实 = 组合根直连信号接线 + B1 Waiting 相位触发(不依赖事件到达序);推荐沿用(单线程回合制,KISS),autoload 全局事件总线为否决倾向。
- 事件规范序:A2 发射序 = 触发格 BFS、8 邻行主序;事件序契约 `flag_judged` < `round_end` 全路径统一。
### 3. 存档/读档路径(MVP 无存档;契约已锁)
- MVP 无元进度持久化(C4/D3 归 Alpha)。**已锁契约**:续玩/复盘快照 = master_seed + 各流 scope 计数器 + 意图序列 + 窗口选择序列;快照切出须发生在无在飞 `open_stream` 实例时(scope 事件边界外——B1 编排保证:切点 = board install 边界)。
- 可回放性已实证:切片冒烟同种子双跑步骤流逐行相同(B1 AC-33 前身)。
- D3 Alpha 期按此契约设计格式;SCHEMA_VERSION 高版本拒读。
### 4. 初始化顺序
```
进程启动
→ autoload 实例化:ConfigDB → RngService(顺序固定;战斗编排器为普通类、由组合根按局实例化——切片形态)
→ ConfigDB._ready():load_tables 加载 + 校验(致命通道;失败 = 构建中止 + 错误清单)
→ 主场景(D1 组合根)装配视图单元 + 信号接线
→ C2 begin_run:RngService.set_master_seed()(唯一接线方;两流计数器全零前提)
→ B1.begin_battle(encounter):set_reload_gate(true) → B3 开域 → B2 spawn → A2 install_board(A1.generate) + B4 roll_board(恰一次)→ A3 notify_round_start
→ 挖掘循环 …… → battle_over 清理序列 → set_reload_gate(false)
→ 关卡末:level_clear()(clear_inventory 恰一次 + 快照计数)
```
---
## API Boundaries
> 完整 API 面(含封闭枚举负空间、布尔受理口径、输入×相位矩阵)以各 GDD AC 前言为**规范源**——本节列边界契约与不变式,实现须与 GDD 逐字一致;冲突时以 GDD 为准并走接口漂移修订。
| 边界 | 契约要点 | 调用方必须遵守的不变式 | 模块保证 |
|---|---|---|---|
| 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;调用序钉死(query_attack→apply_damage→advance;先兑付后衰减) | 禁第二调用方(静态扫描域: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 ↔ B1 | attempt 边界非 scope 事件;actuals p10 为对账输入(非 target 参数) | 编排不把重试当取号 | 生成期指标集输出(实测可推理率 / 实际雷数与安全格数 / 重试次数 / 降级与非法产物标记) |
| B4 → B3 | buff 类经 B1 调 apply_effect(B4 零跨系统读、零直接接口) | 满层拒绝 = 零兑付 + 明细行(无折算) | 授予时数值快照(抗热重载) |
| B1 → D1 | [步骤流行协议](../../design/gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)(15 类封闭枚举 + 公共字段 type/source_class/origin_cell/round_index/display_name/value/highlight? + 特有字段 target/effect_id/pool_value) | 无来源行 = 断言失败;呈现侧禁按行序重演 B2 状态机 | 可回放步骤流(同种子逐行相同) |
**引擎类型核验注记**:接口使用的引擎类型 = Resource / .tres(D2 全表)、RefCounted(容器类)、Signal/Callable(事件载体,ADR-0001)、Control/CanvasLayer/Drawing(D1)、SceneTree headless 协议(测试)——均属 4.x 基线稳定面;4.7 Control 域增量(offset_transform_* 等)已在 breaking-changes.md 登记,D1 采纳前对照核实。
---
## ADR Audit
**现存 ADR:0 份**(`docs/architecture/` 本文档为首个产物;此前架构约束由 GDD 门禁评审 + CLAUDE.md + code-conventions 事实承载)。
### 已锁决策盘点(GDD 层已定稿,不重复立项 ADR)
| 决策 | 锁定来源 |
|---|---|
| GDScript 单语言;autoload = ConfigDB + RngService(定名勿改) | technical-preferences + D2 GDD |
| 全部玩法数值 .tres 单一事实源;零字面量;R1 floor 解析;fail-fast 双通道 | D2 Core Rules 1–8 |
| RNG 封闭流 + scope 派生(R2 黄金向量);可回放 = 硬承诺 | D2 R2 + B1 Core Rule 15 |
| A2 纯规则引擎(零随机/零表读取/零 UI)+ 三事件面封闭 | A2 Core Rule 1/2 |
| B1 编排唯一性 + 七段段序骨架 + 明细行协议 | B1 Core Rule 1/4/14 |
| 多敌 side-wide / 出招确定性(技能池批改型:零随机纪律保持、开轮摇号走 behavior_roll 封闭流)/ 固定顺位 / 伏击 cap=4 | B2(2026-09-09 用户拍板;2026-09-16 技能池批改型) |
| 奖励五值两类交付轴 / install 预分配 / 关卡域库存 / 窗口单件制 | B4(2026-09-11 用户裁决批) |
| weake­n 锚 F0 瞬时当量 / 条款①③⑤闭合对账 owner = B1 | B1 F0–F5 |
| 领域命名禁 ID 前缀 / 组合根模式 / 呈现层契约成文 | CLAUDE.md + docs/code-conventions.md(切片教训批) |
### GDD 登记的"实现期 ADR 裁决项"汇总(= Required ADRs 的来源)
A2 OQ2(事件外发载体)· B1 OQ1(事件总线载体,与 A2 OQ2 同一裁决)· A1 OQ2/OQ3(生成性能姿态 + 度量独立入口)· B2 OQ1(query_attack/ambush_grant_plan 两 face 归属)· B2 OQ5(HeroTable 单行校验落点)· B2 OQ9(零伤害死局跨字段护栏落点)· D2 OQ1(gdUnit4 取舍)· technical-preferences(渲染器选择)· 切片新增正式化批(轮内刷新方案 A / RewardSupplyController / LevelTable / grant_node_active / clear_blast——GDD+registry 修订义务在切片 README)。
### Traceability Coverage Check
Technical Requirements Baseline 63 条 → ADR 覆盖 0 条(无 ADR 存在);按覆盖载体分层:
| 覆盖载体 | TR 数 | 说明 |
|---|---|---|
| ✅ 本架构文档(分层/所有权/数据流/API 边界)+ GDD 契约 | 46 | 数据/RNG/核心行为/编排协议类——已锁且切片实证,无需 ADR |
| 🔶 待 ADR 裁决 | 12 | 引擎/测试/实现形态类:TR-gc-008(渲染器)、TR-a1-011/012(生成姿态)、TR-d2-014/016(测试载体)、TR-a3-004+TR-b1-001/002(事件载体)、TR-b2-005/006(face 归属)、TR-d2-010+TR-gc-002(D3 契约正式化) |
| ⏳ 未设计系统预留 | 5 | A5/C1–C4/D3 关联 TR(随各系统 GDD 立项) |
**结论:0 条孤儿需求**——46 条由本文档+GDD 承载、12 条待 ADR(见下)、5 条随未设计系统。**2026-09-12 更新:ADR-0001~0004(Foundation 四项)已落定 → 正式工程骨架解冻**;ADR-0005~0008 随对应系统开工前裁决(切片代码可作为参考实现,生产化迁移待 ADR-0008 正式化批)。
---
## Required ADRs
### 必须在正式编码开始前(Foundation 裁决)——✅ 已全部落定(2026-09-12,solo 授权代决,godot-specialist 校验通过)
1. **✅ ADR-0001 `event-bus-and-orchestration-carrier`**(`adr-0001-event-bus-and-orchestration-carrier.md`):组合根直连 signal 接线 + B1 Waiting 相位触发;autoload 全局事件总线否决。specialist 增补两条静态规则(方法 Callable only / 发射者禁反向引用)。
2. **✅ ADR-0002 `renderer-and-window-config`**(`adr-0002-renderer-and-window-config.md`):gl_compatibility(绕开 D3D12 新驱动线);基座 1280×800 + canvas_items + expand 显式落盘。
3. **✅ ADR-0003 `scene-structure-and-board-rendering`**(`adr-0003-scene-structure-and-board-rendering.md`):autoload 仅 {ConfigDB, RngService};core RefCounted / presentation 九单元组合根;**棋盘渲染 MVP 沿用切片逐格控件形态**(specialist 纠正:切片并非自绘 _draw——迁移登记为 D1 候选工作项含探针改造);CJK 字体随包为硬性前置。
4. **✅ ADR-0004 `test-carrier-and-ci`**(`adr-0004-test-carrier-and-ci.md`):双载体 + 静态扫描域 S1–S8(specialist 增补 S2 全局 rand* 禁令 / S5 core 禁裸引用 autoload 标识符)+ CI 外部超时;gdUnit4 deferred(D2 OQ1 关闭形态)。
实现级立场已登记 `docs/registry/architecture.yaml`。
### 相关系统开工前应有
5. `/architecture-decision "board-generation-performance"` → ADR-0006:A1 生成性能与异步化(F2c 常数 C 定稿;切片证据 = 10×10/12×12 同步生成可接受——推荐 MVP 同步+加载幕,WorkerThread 留扩展);度量独立入口确认(切片已实现三入口)。
6. `/architecture-decision "unit-api-face-ownership"` → ADR-0007:B2/B1 两 face 归属(query_attack / ambush_grant_plan)+ HeroTable 单行校验落点。
7. `/architecture-decision "zero-damage-guard-locus"` → ADR-0008:零伤害死局跨字段/跨表护栏落点(推荐落 D2 跨表校验——切片 LevelTable 校验⑧先例)。
8. `/architecture-decision "slice-mechanism-formalization"` → ADR-0009:切片新增机制正式化批(轮内刷新方案 A / RewardSupplyController / LevelTable / grant_node_active / clear_blast)——GDD+registry 接口漂移修订的 ADR 化登记。
### 已落定(2026-09-12 追加)
0. **✅ ADR-0005 `save-contract-and-schema-versioning`**(`adr-0005-save-contract-and-schema-versioning.md`):Foundation 层收口——SCHEMA_VERSION 比较归属 + RNG 快照切点契约 ADR 化(TR-d2-010 / TR-gc-002),Foundation 零缺口闭合。
### 可推迟
- ADR-0010 A1 EC5 运行期熔断(连续降级确定性回退已知好种子)
- ADR-0011 B5 战斗舞台(棋盘上 vs 独立场景)——"战斗表现美术测试"后裁决
- ADR-0012 A5 探测显影接口
- ADR-0013 D3 存档格式(RNG 快照契约已由 ADR-0005 锁定,格式归 Alpha)
- C2/C3/C4 结构性决策随各系统 GDD 立项
---
## Architecture Principles
1. **Core 纯逻辑、headless 完整可跑**——Core/Feature 玩法逻辑零 Control/场景引用、零 UI 依赖;`--headless --script` 可跑完整战斗(D2 Rule 7 工程债;切片 88 冒烟实证)。输入是意图对象、输出是事件流/步骤流,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 探针测试为标配(切片缺陷 ①②④⑤ 教训沉淀)。
---
## Open Questions
| ID | Summary | Priority | Resolution Path |
|----|---------|----------|-----------------|
| QQ-01 | 渲染器选择(Forward+ vs gl_compatibility)与项目窗口基线 | High | ADR-0002(正式骨架前) |
| QQ-02 | 事件总线载体与 A2 事件捕获测试 API | High | ADR-0001(正式骨架前) |
| QQ-03 | gdUnit4 是否引入并迁移冒烟用例 | Medium | ADR-0004(可推迟到正式 CI 期;D2 OQ1) |
| QQ-04 | 轮内刷新数值连锁(清图支配性条款⑤ / 真人刷图行为获取期望)重算 | Medium | playtest 实测 + ADR-0008 正式化批 |
| 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 前) | 战斗表现美术测试 → ADR-0010 |
| QQ-09 | C3 消费 B1 对账输出的事实依赖回写 systems-index | Low | C3 GDD 定稿时 |
| QQ-10 | `.claude/docs/director-gates.md` 缺失(TD-ARCHITECTURE/LP-FEASIBILITY 判据无载体) | Low | 补建 gate 定义文件或维持 solo/lean 模式约定 |
@@ -0,0 +1,102 @@
# 战斗棋盘 Node2D 渲染迁移方案(Phase A:仅棋盘)
> 状态:**草稿待审**(2026-09-23 起草,board-node2d-plan-20260923 worktree)。
> 上位裁决:ADR-0006(分工与形态)、用户 2026-09-23 拍板「Node2D 重构只迁战斗棋盘,开始菜单/各覆盖层保持 Control;战斗画面 Node 后续批完成」。
> 本文档是 Phase A 实施批的工程方案;批准后实施批从本文开工,不再另起炉灶。
## 0. 一句话
把 D 区棋盘本体从「GridContainer + 逐格 Button」换成「Control 壳 + SubViewport + Node2D 世界」,BoardView 类名与公共面契约逐项保持——探针与组合根零改造,素材终稿到位后 1:1 换图零返工。
## 1. 目标形态(已与用户确认的三块模型)
战斗屏 = 两块 Node2D 舞台 + 一层 Control 面板。术语校准:Godot 没有「Canvas」节点——面板层是 Control 树(常驻顶层与弹层经 CanvasLayer,现状已如此);Node2D 不参与 Control 容器布局,所以每块舞台包一个 Control 壳(SubViewportContainer)参与屏幕栅格布局,壳内才是 Node2D 世界。
```
BattleScreen(Control 栅格;目标形态 = B/C/D 三区——进度条退场裁定见下)
├── B 战斗画面(Phase B 改造,本批不动)
│ ├── Node2D 舞台壳:主角/敌人站桩、意图动画、演出(后续批)
│ └── Control 半区:结算明细带、回合指示、战斗日志(永久 Control)
├── C 角色信息 .................. Control(不动)
└── D 扫雷图
├── AP 条、按钮组、toast .... Control(不动)
└── BoardView(本批改造对象)
└── SubViewportContainer(Control 壳,参与布局)
└── SubViewport(逻辑分辨率 = 格边长 × 板尺寸)
└── BoardWorld(Node2D)
└── CellUnit × N(Node2D,每格一个)
```
**进度条退场裁定(2026-09-23 用户)**:战斗画面不再有关卡进度条——战斗结束后返回关卡画面,节点链进度只留关卡画面。
**A 区代码命运(2026-09-23 追问裁定):退役,不迁移。关卡画面不重新实现。**
- **关卡画面 = RouteOverlay 升级**:现役选路覆盖层已是整幅节点图 + 全套节点态语义(暗金已过/金当前/暗未达/亮金候选通道/紫赤 Boss 边(旗朱→紫赤 2026-09-26 封印紫批),与压缩条同一组 UiKit token)+ 候选按钮 + 放弃入口。停点前移批只改开合时机(关卡开始即开、战后返回即开)与流程接线(选路确认不再经兑付停点绕行),渲染本体零重写。
- **LevelStripView 不迁移的理由**:它唯一的存在理由 = 战斗屏上看不到整图时的 28px 压缩常显替代面。地图页上压缩条冗余(整图即进度),战斗屏又已裁定零进度显示——迁过去没有消费者。其语义无独有资产(色板/节点态与 RouteOverlay 同族),单链退化渲染(全链横条)被整图布局天然取代。
- **唯一继承面 = 逻辑层数据面**:runner 只读出口(`nodes()` / `visited_ids()` / 节点编号空间 / `next_candidates()`)零改动——压缩条与整图本来就消费同一份数据,这正是"退役不迁移"成立的根据。
排程事实与修订面:
1. **A 区现状代码继续服役**:当前流程尚未倒置(冒险开局直进战斗,节点图是战后弹层),A 区顶栏是战斗过程中唯一的节点链可视面——先删会致盲。退场落地 = 停点前移批,届时四区→三区。
2. **停点前移批的删除面清单**(退役随该批执行):`LevelStripView` 类、ui_assembler 组装行(:28)、main.gd `strip_view` 引用(:52 等)、ui_probe 两处断言(:77–78 节点条构建检查、:188 报告行)、battle.md A 区条款。
3. **该批的 GDD 修订面**:HUD框架.md 四区结构表(含 B/C/D 是否重新编字母)、关卡节点地图.md 节点链常显条款(常显载体随进度条退役,由关卡画面本体承接)、battle.md 像素布局表(28px 纵向空间再分配)。
## 2. Phase A 范围(本批做)
1. **BoardView 内部重构**(`presentation/views/board_view.gd`):
- 根仍是 Control(类名/extends 不变),子结构 GridContainer → SubViewportContainer + SubViewport + BoardWorld。
- `_cells`:Vector2i → CellUnit(Node2D)字典,键面与现 Dict 同构(探针断言面,见 §4)。
- CellUnit 内部固定叠层序:砖面底 < 变异纹带 < 数字/徽章 < 旗 < 焦点高亮——本批全程序化绘制(`_draw`),等价平移现行视觉;每个叠层留 `texture` 槽位(§8.2 暂定档 32×32),终稿纹理换入即用。
- 现有行为逐项平移:呼吸微光(StyleBoxFlat 边框 → `_draw` 边框)、悬停反白、路径预览、危险红、双击 chord 检测、`set_desaturated`(ShaderMaterial 挂 BoardWorld 材质)。
2. **输入换板**:BoardView 壳层 `_gui_input` 接鼠标 → 格坐标单点公式 `floor(local_pos / cell_edge)` → 复用现有 hover/click/chord/长按逻辑;`action_requested` 信号链不变。
3. **`cell_size` 只读属性增补**:现私有名 `_cell`,ui_layout_probe `get("cell_size")` 读到 null——补只读属性,顺手修正。
4. **push_input 端到端输入探针**(新增,ADR-0003 登记项转义务):合成鼠标事件经真实输入路径打 SubViewportContainer,验悬停/左键/右键/chord 四 case——现探针全走 `click_cell` 直调,公式换算无覆盖,这是唯一新增自动化面。
5. **冒烟全量回归 + 文档随批**:HUD框架.md 渲染技术注记(v0.x 版本行)、battle.md 实现注记(格尺寸公式锚点不动)、architecture.md BoardView 形态行、ui_kit 同步面清点。
**不做**(明确出批):B 区舞台任何改动;素材终稿接入;格边长数值调整(归样张批);键盘寻格/无障碍(沿 ADR-0003 已接受口径)。
## 3. Phase B(2026-09-26 已落地——战斗舞台 Node2D 迁移批,台账 170-173)
- BattleStageView 同构改造**已实施**:PanelContainer → Control 壳(类名/252px/树内位置不变)+ Node2D 舞台半区(SubViewportContainer/SubViewport/StageWorld)+ Control 文本半区(round_label/stage_center/log_view 三件保留);站桩 StandeeView(Control)退役删除,本体 = StandeeBody(Node2D 世界精灵,行为面 17 方法契约保持);slot_row+悬停 tooltip 保留 Control 载体挂壳层(伴随件容器 = enemy_box 语义改写)。
- Phase A 交付的两挂点已被 Phase B 复用:set_world_font 字体显式注入(C6 判据=注入非空+has_char+宽度>0)与坐标空间分界纪律(世界=局部排布,壳层换算单点 world_to_shell)。
- **样张硬前置改判(用户 2026-09-26 裁决①)**:样张批冻结只卡换皮/演出批(循环 2 前置),不卡结构迁移——本节原「Phase B 开工硬前置 = 样张批冻结」条款据此作废;ADR-0006「只卡换皮不卡开工」原则的落文位。
## 4. 探针与测量面(实证清单)
| 探针/工具 | 耦合点(实证) | Phase A 处置 |
|---|---|---|
| ui_probe | `board_view._cells.is_empty()`(:73)、`_cells.size()` 对板宽高(:187)、`board()`/`set_desaturated()` | `_cells` 键面同构即绿,零改 |
| ui_click_probe | `click_cell(cell, button)` 直调(:257–277)、`_cells.is_empty()`(:97);**无合成鼠标事件**(grep parse_input_event/push_input 零命中) | 公共方法保留即绿,零改 |
| ui_layout_probe | `get("cell_size")`(:39,现读 null)、`board()` | 增补只读属性(§2.3) |
| smoke 行协议同步闸 | 战斗日志 9 类行格式化(smoke_all.gd:3325+)——**是日志行不是棋盘** | 零影响,全量回归覆盖 |
| MC / 模拟试玩 / verifier | core/ 逻辑层 | 零接触零影响 |
## 5. 性能与预算
- draw calls:CellUnit 同材质 `_draw` 走 2D 批处理,144 格占位态预计个位数批次;预算 < 200 维持(HUD框架 引用值),实施批用 RenderingServer 实测数回填。
- 进入战斗屏 500ms 可交互:SubViewport 创建在 `_ready` 一次完成,无逐帧重建;`set_cell_area`/`rebuild` 只重排子节点。
- SubViewport 逻辑分辨率 = 格边长 × 板尺寸(≤ 36px × 14 格量级),渲染目标显存占用可忽略;低端机帧预算实施批实测。
## 6. 风险与对策
| 风险 | 对策 |
|---|---|
| SubViewport 内 Label 不继承主场景主题(数字/中文渲染空白或字体错) | 实施批第一小步先做 theme/字体显式注入挂点并用 CJK 冒烟验证——ADR-0006 Verification Required 项 |
| 45/36px 锚点 × 32px 格源非整倍缩放出现像素纹路 | 架构只保证 `set_cell_area` 单点可调;数值归样张批(圣经 §8.2 v1.9 注记①路线),本批不裁决 |
| `_gui_input` 与 toast/AP 条控件输入抢占 | D 区内部布局不变(Control 兄弟件照旧),棋盘壳是叶节点,抢占面与现 GridContainer 相同;探针四 case 覆盖 |
| 改造期行为漂移(呼吸微光/chord 时序等隐性手感) | 功能等价清单(ADR-0006 Validation)逐项人工核对 + 探针自动化面 |
## 7. 排期与依赖
| 项 | 估时 | 依赖 |
|---|---|---|
| 本起草批(ADR-0006 + 本方案 + ADR-0003 注记) | 已完成,待审 | 无 |
| Phase A 实施批 | 1–2 天(含探针增补 + 冒烟回归 + 文档随批) | 本方案批准 |
| 样张批(素材侧,另线) | 未排 | 主角候选表态 + 复用批审阅 |
| Phase B 实施批 | 未排(同构复制,估时随 Phase A 实测修正) | Phase A 落地 + 样张批冻结 |
与在飞批的关系:曲线调参双刀扫描(worktree hp-bonus-scan-20260923)与本批文件集零交集;UI 复用候选审阅是素材侧独立线,互不阻塞。
## 8. 验收(实施批完成定义)
ADR-0006 Validation Criteria 全项:冒烟全量绿 + 三探针绿 + 新增 push_input 探针绿 + 功能等价清单逐项可验 + draw calls < 200 实测回填 + 两档分辨率装板校验(12×12 与 10×10 各验)。
@@ -0,0 +1,132 @@
# 代码架构审查报告 — 代码组织与封装
> 日期:2026-09-13
> 载体:`game/`(9707 行 GDScript:core 12 文件 / data 11 / presentation 28 / smoke 10)
> 基线:`docs/architecture/architecture.md` v1.0 + ADR-0001~0005 + `docs/code-conventions.md`
> 触发:用户在 code-review 之后要求补充「代码组织问题 + 封装问题」专项审查
> 审查方式:core/presentation 关键文件全读 + S1–S8 静态规则逐条 grep 验证 + 跨层引用面扫描
>
> **Verdict: CONCERNS** — 分层纪律实测扎实(S1–S7 零违例、仅 2 处已登记例外),但封装收口存在
> 一条系统性欠账:核心模块内部状态以公有字段暴露,呈现层已形成链式穿透的既成事实。
> 原型期无害;是 ADR-0009「切片机制正式化批」应收口的主题。
---
## 1. 正面观察(实测合规项)
| 规则 | 结论 | 证据 |
|---|---|---|
| S1/S2 零随机 | ✅ | core 内唯一 `RandomNumberGenerator.new` 在 rng_service.gd:76(所有者本体);`stream.randf()`(reward_supply_controller.gd:32)取号经 B4 注入封闭流 |
| S3 零表加载 | ✅ | core 内唯一 `load()` 在 config_db.gd:81(单点本体) |
| S4 core 零 UI | ✅ | core/ 无 Control/Node2D/.tscn 引用(grep 命中均为 `RewardSupplyController` 类名误报与注释) |
| S5 零裸引用 autoload | ✅ | core/ 内 ConfigDB/RngService 命中均为日志前缀字符串 |
| S6 core↔core 方法 Callable | ✅ | level_runner.gd:130-134 全部方法转发;lambda 仅用于 sort_custom(不捕获 self) |
| S7 编排唯一性 | ✅ | B2/B3/B4 调用面 core 内收口于 battle_orchestrator.gd;仅 level_runner.gd:112(grant_node_active,已登记接口漂移)与 :168(in_stock_count 只读)两处例外 |
| 呈现层零核心引用 | ✅ | 28 个视图/装配器文件中仅 main.gd(组合根)与 hud_presenter.gd(授权翻译点)触 core |
| 封装典范 | — | status_container / reward_supply_controller / b4.query_inventory():私有状态 + 投影查询 + 授予时快照;battle_orchestrator.diagnostic_snapshot()(:660)为「成文只读出口」正确先例 |
---
## 2. 封装问题(按根因 → 表征排序)
### E1(根因)编排器协作对象全公开,呈现层链式穿透已成既成事实 — P1
- **位置**:core/battle_orchestrator.gd:20-27(`var a1/a2/a3/b2/b3/b4` 全公有)
- **表征**:`b1.a2.board`、`b1.b2.hero.hp`、`b1.a3.phase` 式层层下钻——main.gd 18 处、hud_presenter.gd 15 处。
- **影响**:组合根与 presenter 是授权翻译点,但访问形态是「恰好字段公开」而非「B1 提供成文读取面」。
S7 只能做文件级扫描(防另写调用方),防不了呈现层意外调用写 API,也防不了读取路径随重构漂移。
- **建议**:B1 补聚合呈现快照/查询面(`hud_state()` 一类,先例 = `diagnostic_snapshot()`);
a2–b4 下划线化后经命名代理暴露只读操作。
### E2(表征)视图绕过 A2 查询面直读原始状态字典 — P1
- **位置**:core/minesweeper_rules.gd:18-20(`board/revealed/flags` 公开可变);
presentation/views/board_view.gd:278、:303 直接 `_a2.revealed.get(c, {})`
- **矛盾点**:board_view.gd:7 头注自称「只读 A2 查询面」——直读 `.revealed` 恰是绕过查询面。
B1 自身亦直读(battle_orchestrator.gd:565-569 `a2.board.width/height`)。
- **建议**:`revealed/flags` 私有化;补 `reveal_record(c) -> Dictionary`、`board_dimensions()` 查询面。
调用点仅 B1 + board_view + main 三处,成本很低。
### E3(表征)B2 裸字典句柄跨模块传递,API 缺「含死亡残位」查询面 — P2
- **位置**:core/unit_container.gd:13-14(`hero/enemies` 公开;句柄 = Dictionary 本体,任何持有者可
`u.hp = 999` 绕过 apply_damage)
- **现症**:hud_presenter.gd:78-79 为渲染死亡残位直读 `b1.b2.enemies` 原始数组——
B2 的 `query_alive` 不含死亡单位,API 缺口迫使呈现层越界(B2 头注已登记此痛点)。
- **建议**:短期补 `query_all_units()` 投影面把 presenter 需求收进 API;
正式化(ADR-0007 两 face 归属裁决时)再决定是否升级 Unit 类型。
### E4 `db.hero` 表值运行时改写——测试覆盖通道用共享可变单例传参 — P2
- **位置**:presentation/main.gd:327-329、core/level_runner.gd:60-61,75-76 直接写 `db.hero.atk/max_hp`;
B2.spawn 经读表间接获得加成值
- **前科**:双写叠加 6→18→54 教训、双基线同步义务(main.gd:187-189 注释)——纪律已成文但机制脆弱。
- **建议**:切片保留;将「运行时表可变」登记进 ADR-0009 候选;正式化改参数传递(`spawn(atk_override=...)`)。
### E5 「只读」字段仅靠纪律,无机制约束 — P2
- **位置**:core/action_economy.gd:17-20(`phase/ap/rap/last_round_end` 架构文档定义为只读,实际公开可变);
core/rng_service.gd:19(`master_seed` 可绕过 set_master_seed 的计数器全零断言)
- **现状**:当前调用方全部合规(A2 经 try_spend/request_dig,B1 经 notify_*,呈现层只读)。
- **建议**:下划线化 + 查询方法(`phase()/ap()/rap()`),趁调用方少时收口,成本极低。
### E6 小项 — P3
| 项 | 位置 | 说明 |
|---|---|---|
| 返回类型多态 | core/reward_economy.gd(grant/stock/use 失败返 `false`,query_* 返 `null`) | B1 消费处被迫 `if not (snap is Dictionary)`;统一 `null` 更符合 GDScript 惯例 |
| 泄漏原始表行 | core/reward_economy.gd:256-265 `equivalence_supply` 返回 `db.reward.rows` 行引用(可变) | 工具观测面,建议 `duplicate()` |
| BoardData 全公开 | core/board_data.gd:8-18「生成后不可变」仅靠注释;视图持板即可读全部雷位(当前未作弊) | 正式化时 duplicate 冻结或下划线化 + 访问器 |
| 成员声明位置 | core/action_economy.gd:121 `var _next_round_mode` 声明在中部、使用在前 | 集中到顶部 |
---
## 3. 代码组织问题
### O1 main.gd 回涨至 667 行,再越 ~400 行审视线(规范 1.1)— P1
- **位置**:presentation/main.gd(667 行);ui_assembler.gd:3 自记「回涨 761 行触发审视线——拆出」历史
- **增长主因**:调试模式(`_start_single/_start_debug_levels/_start_battle/_on_battle_finished` 的
`_level_mode` 分支约 100+ 行)糊在组合根。
- **建议**:照 tune_sim_controller.gd 先例把调试模式抽成独立控制器,组合根回到纯接线。
### O2 调试模式与 LevelRunner 平行实现,语义漂移风险 — P1
- **位置**:「库存跨战斗延续 + HP 携带 + 层级推进」存在两份实现——core/level_runner.gd(正式)与
main.gd 调试分支(历史入口);`level_clear()` 调用时点已各有细节(main.gd:367,376 vs level_runner.gd:167)。
- **建议**:调试两层模式直接复用 LevelRunner(其设计目标即此),或抽 `DebugBattleSession`。
### O3 B1 体量与纯函数沉淀 — P2
- **位置**:core/battle_orchestrator.gd 718 行(审视线超 300+);
伤害公式族 `_resolve_damage/_charge_damage_component/_armor_effectiveness/_clamped_pool`
(约 80 行)为零状态纯算术。
- **建议**:按规范 1.2 抽 `DamageMath` 静态宿主类;段序骨架不动、可回放承诺无风险。
- **顺带**:core/board_generator.gd:50-55 对同一张合法板调用两次 `measure_inferability`
(全库最贵求解器)——合并为一次。
### O4 呈现层反向依赖 smoke 工具域 — P2
- **位置**:presentation/tune_sim_controller.gd:23 `load("res://smoke/level_autoplay.gd")`——
UI 控制器依赖测试工具目录。
- **建议**:「关卡自动模拟循环」上移为共享组件(core 或 lib),smoke 与 tune_sim 共同消费。
- **顺带**:main.gd:106,188 与 tune_sim_controller.gd:23 用运行时字符串 `load()`,
与 main.gd:16-19 的 `preload` 常量风格混用——统一 `preload`(编译期路径校验)。
### O5 头注溯源缺失(规范 1.4)— P3
core/ 12 文件中 4 个缺标准 `# 实现 GDD:...` 行:
`board_data.gd`(有 A1 描述但非标准格式)、`config_db.gd`、`rng_service.gd`、`reward_supply_controller.gd`。
---
## 4. 建议行动顺序
1. **P1 三连(合计约 2 天)**:
E2(A2 状态私有化 + 查询面)→ E1(B1 呈现快照面)→ O1+O2(调试模式抽离/复用 runner)。
完成后 board_view / hud_presenter / main 与 core 的接触面全部变为命名 API,封装收口完成。
2. **P2 项并入 ADR-0009「切片机制正式化批」裁决**:E4 表值改写参数化、E3 单位句柄形态、
O3 DamageMath 拆分、O4 模拟循环上移。
3. **P3 顺手清**(头注补齐、返回值统一、声明位置、preload 统一)。
> 复审触发:P1 三连落地后重跑本审查核对接触面收口;ADR-0009 落定后正式工程骨架迁移前复核 E3/E4 裁决结果。
@@ -0,0 +1,413 @@
{
"schema_version": 1,
"diagram_type": "architecture",
"meta": {
"title": "Dungeon Sweeper · GDD 概念结构图",
"output": "docs/architecture/gdd-concept-map.html",
"quality_profile": "showcase",
"locale": "zh-CN",
"viewBox": [
1364,
660
],
"views": [
{
"id": "core-loop",
"label": "核心循环主链",
"focus": [
"board-gen",
"minesweeper",
"action-economy",
"settlement",
"adventure",
"difficulty"
],
"note": "主路径:新雷图 → 挖掘计费 → 轮末结算 → 关卡推进 → 难度抬升。"
},
{
"id": "settlement-hub",
"label": "结算枢纽与红线",
"focus": [
"settlement",
"enemy",
"status",
"rewards",
"hud"
],
"note": "战斗结算是战斗域唯一调用方;呈现层只订阅结算步骤流。"
},
{
"id": "foundation",
"label": "支撑底座",
"focus": [
"data-config",
"board-gen",
"settlement",
"hud"
],
"note": "ConfigDB 单点 + RngService 四流,被全部系统取用。"
}
]
},
"components": [
{
"id": "board-gen",
"type": "backend",
"label": "棋盘生成",
"sublabel": "可推理·可通关",
"pos": [
50,
300
],
"size": [
130,
64
]
},
{
"id": "minesweeper",
"type": "backend",
"label": "排雷玩法",
"sublabel": "挖格·插旗·连锁",
"pos": [
240,
300
],
"size": [
136,
64
]
},
{
"id": "action-economy",
"type": "backend",
"label": "行动经济",
"sublabel": "AP 账本·停手/刷新",
"pos": [
436,
300
],
"size": [
140,
64
]
},
{
"id": "settlement",
"type": "backend",
"label": "战斗结算",
"sublabel": "轮末编排·逐行兑现",
"pos": [
648,
300
],
"size": [
156,
72
]
},
{
"id": "adventure",
"type": "backend",
"label": "冒险结构",
"sublabel": "关卡序列·大奖励",
"pos": [
952,
300
],
"size": [
136,
64
]
},
{
"id": "difficulty",
"type": "backend",
"label": "难度曲线与Boss",
"sublabel": "期望值带·Dmax",
"pos": [
1152,
300
],
"size": [
150,
64
]
},
{
"id": "aux-items",
"type": "backend",
"label": "魔法道具",
"sublabel": "显影·探测类",
"pos": [
240,
140
],
"size": [
136,
64
]
},
{
"id": "enemy",
"type": "backend",
"label": "敌人系统",
"sublabel": "敌人数据·意图",
"pos": [
651,
140
],
"size": [
150,
64
]
},
{
"id": "hud",
"type": "frontend",
"label": "HUD框架",
"sublabel": "呈现契约·布局",
"pos": [
244,
470
],
"size": [
136,
64
]
},
{
"id": "data-config",
"type": "database",
"label": "数据配置",
"sublabel": "ConfigDB·RngService",
"pos": [
37,
470
],
"size": [
156,
64
]
},
{
"id": "status",
"type": "backend",
"label": "增益状态",
"sublabel": "容器·叠层·衰减",
"pos": [
614,
470
],
"size": [
130,
64
]
},
{
"id": "rewards",
"type": "backend",
"label": "地图战斗资源",
"sublabel": "格面资源·清图奖励",
"pos": [
776,
470
],
"size": [
136,
64
]
}
],
"boundaries": [
{
"kind": "region",
"label": "排雷循环",
"wraps": [
"board-gen",
"minesweeper",
"action-economy",
"aux-items"
]
},
{
"kind": "region",
"label": "战斗循环",
"wraps": [
"settlement",
"enemy",
"status",
"rewards"
]
},
{
"kind": "region",
"label": "冒险循环",
"wraps": [
"adventure",
"difficulty"
]
},
{
"kind": "region",
"label": "表现与支撑",
"wraps": [
"data-config",
"hud"
]
}
],
"connections": [
{
"id": "gen-to-mines",
"from": "board-gen",
"to": "minesweeper",
"label": "生成雷图",
"variant": "emphasis"
},
{
"id": "mines-to-ap",
"from": "minesweeper",
"to": "action-economy",
"label": "挖掘花费 AP",
"variant": "emphasis"
},
{
"id": "ap-to-settle",
"from": "action-economy",
"to": "settlement",
"label": "round_end",
"variant": "emphasis"
},
{
"id": "settle-to-enemy",
"from": "settlement",
"to": "enemy",
"label": "兑现敌人行动"
},
{
"id": "settle-to-status",
"from": "settlement",
"to": "status",
"label": "兑现增益减益",
"fromSide": "bottom",
"toSide": "top",
"labelAt": [
679,
447
]
},
{
"id": "settle-to-rewards",
"from": "settlement",
"to": "rewards",
"label": "兑现格面奖励",
"fromSide": "bottom",
"toSide": "top"
},
{
"id": "settle-to-adv",
"from": "settlement",
"to": "adventure",
"label": "结果推进关卡",
"variant": "emphasis"
},
{
"id": "adv-to-diff",
"from": "adventure",
"to": "difficulty",
"label": "关卡定级",
"variant": "emphasis",
"labelDy": 14
},
{
"id": "diff-to-enemy",
"from": "difficulty",
"to": "enemy",
"label": "数值与 Boss Dmax"
},
{
"id": "mines-to-aux",
"from": "minesweeper",
"to": "aux-items",
"label": "挖掘期使用"
},
{
"id": "base-to-gen",
"from": "data-config",
"to": "board-gen",
"label": "种子与表值",
"labelAt": [
115,
395
]
},
{
"id": "base-to-settle",
"from": "data-config",
"to": "settlement",
"label": "RNG 流·表值",
"fromSide": "top",
"toSide": "bottom"
},
{
"id": "settle-to-hud",
"from": "settlement",
"to": "hud",
"label": "结算步骤流",
"variant": "dashed",
"fromSide": "left",
"toSide": "top"
},
{
"id": "adv-to-gen",
"from": "adventure",
"to": "board-gen",
"label": "推进下一关",
"variant": "dashed",
"fromSide": "bottom",
"toSide": "left",
"via": [
[
1020,
618
],
[
15,
618
],
[
15,
332
]
]
}
],
"cards": [
{
"dot": "cyan",
"title": "核心循环",
"items": [
"主路径:棋盘生成 → 挖掘计费 → 轮末结算 → 关卡推进 → 难度抬升",
"难度参数流入下一关雷图,虚线「推进下一关」闭合整局循环"
]
},
{
"dot": "emerald",
"title": "结算枢纽与架构红线",
"items": [
"战斗结算是敌人系统、增益状态、地图战斗资源的唯一调用方",
"呈现层只订阅战斗结算步骤流,不直订排雷玩法事件"
]
},
{
"dot": "amber",
"title": "支撑底座与未上图",
"items": [
"数据配置(ConfigDB + RngService 四流)被全部系统引用,图中只画主干两笔",
"未上图(已定稿):地形变异、存档序列化、战斗表现、音频、教学引导、设置与本地化",
"范围草案:关卡节点地图、元进度"
]
}
]
}
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

@@ -0,0 +1,32 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Archify automated browser evidence · gdd-concept-map.html</title>
<style>
*{box-sizing:border-box}body{margin:0;padding:24px;background:#e9eef5;color:#172033;font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}header{max-width:1500px;margin:0 auto 18px}h1{margin:0 0 6px;font-size:20px}p{margin:0;color:#526176}.grid{max-width:1500px;margin:auto;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px}figure{margin:0;padding:10px;background:white;border:1px solid #c9d4e3;border-radius:12px;box-shadow:0 10px 30px rgba(15,23,42,.08)}img{display:block;width:100%;height:auto;border:1px solid #e2e8f0}figcaption{padding:9px 4px 2px;color:#526176}@media(max-width:900px){.grid{grid-template-columns:1fr}}
</style>
</head>
<body>
<header><h1>Automated browser evidence</h1><p>gdd-concept-map.html · visual-check containment pass · perceptual visual review pending</p></header>
<main class="grid">
<figure>
<img src="gdd-concept-map.visual-check.1440x900.light.png" alt="light 1440 by 900">
<figcaption><strong>LIGHT</strong> · 1440×900 · containment pass</figcaption>
</figure>
<figure>
<img src="gdd-concept-map.visual-check.1440x900.dark.png" alt="dark 1440 by 900">
<figcaption><strong>DARK</strong> · 1440×900 · containment pass</figcaption>
</figure>
<figure>
<img src="gdd-concept-map.visual-check.2048x1320.light.png" alt="light 2048 by 1320">
<figcaption><strong>LIGHT</strong> · 2048×1320 · containment pass</figcaption>
</figure>
<figure>
<img src="gdd-concept-map.visual-check.2048x1320.dark.png" alt="dark 2048 by 1320">
<figcaption><strong>DARK</strong> · 2048×1320 · containment pass</figcaption>
</figure>
</main>
</body>
</html>
@@ -0,0 +1,548 @@
{
"schemaVersion": 1,
"ok": true,
"command": "visual-check",
"evidenceKind": "automated-browser",
"status": "pass",
"visualReview": "pending",
"artifact": {
"path": "D:\\code\\g-plan2\\docs\\architecture\\gdd-concept-map.html",
"sha256": "aa7ea12deda2e43cc4d95e8550057f0e4e4d9dc617a968410cd55a0f454dc295",
"bytes": 817914
},
"state": {
"detail": "read",
"motion": "still"
},
"chrome": {
"status": "available",
"executable": "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"
},
"diagnostics": [],
"containment": {
"status": "pass",
"viewports": [
{
"width": 1440,
"height": 900,
"theme": "light",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1117,
"diagramWidth": 1087,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 7.172287390029325,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1600,
"height": 1000,
"theme": "light",
"innerWidth": 1600,
"innerHeight": 1000,
"scrollWidth": 1600,
"scrollHeight": 1000,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1154,
"diagramWidth": 1124,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 7.41642228739003,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1920,
"height": 1080,
"theme": "light",
"innerWidth": 1920,
"innerHeight": 1080,
"scrollWidth": 1920,
"scrollHeight": 1080,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1320,
"diagramWidth": 1290,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 8.511730205278592,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 2048,
"height": 1320,
"theme": "light",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1819,
"diagramWidth": 1769,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 9,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
}
]
},
"readability": {
"status": "pass",
"minimumProjectedNodeTextPx": 6,
"viewports": [
{
"width": 1440,
"height": 900,
"theme": "light",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1117,
"diagramWidth": 1087,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 7.172287390029325,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1600,
"height": 1000,
"theme": "light",
"innerWidth": 1600,
"innerHeight": 1000,
"scrollWidth": 1600,
"scrollHeight": 1000,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1154,
"diagramWidth": 1124,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 7.41642228739003,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1920,
"height": 1080,
"theme": "light",
"innerWidth": 1920,
"innerHeight": 1080,
"scrollWidth": 1920,
"scrollHeight": 1080,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1320,
"diagramWidth": 1290,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 8.511730205278592,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 2048,
"height": 1320,
"theme": "light",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1819,
"diagramWidth": 1769,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 9,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
}
]
},
"viewerChrome": {
"status": "pass",
"viewports": [
{
"width": 1440,
"height": 900,
"theme": "light",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1117,
"diagramWidth": 1087,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 7.172287390029325,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1600,
"height": 1000,
"theme": "light",
"innerWidth": 1600,
"innerHeight": 1000,
"scrollWidth": 1600,
"scrollHeight": 1000,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1154,
"diagramWidth": 1124,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 7.41642228739003,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1920,
"height": 1080,
"theme": "light",
"innerWidth": 1920,
"innerHeight": 1080,
"scrollWidth": 1920,
"scrollHeight": 1080,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1320,
"diagramWidth": 1290,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 8.511730205278592,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 2048,
"height": 1320,
"theme": "light",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1819,
"diagramWidth": 1769,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 9,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
}
]
},
"captures": {
"status": "pass",
"screenshots": [
{
"width": 1440,
"height": 900,
"theme": "light",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1117,
"diagramWidth": 1087,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 7.172287390029325,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light",
"file": "gdd-concept-map.visual-check.1440x900.light.png"
},
{
"width": 1440,
"height": 900,
"theme": "dark",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1117,
"diagramWidth": 1087,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 7.172287390029325,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "dark",
"file": "gdd-concept-map.visual-check.1440x900.dark.png"
},
{
"width": 2048,
"height": 1320,
"theme": "light",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1819,
"diagramWidth": 1769,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 9,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light",
"file": "gdd-concept-map.visual-check.2048x1320.light.png"
},
{
"width": 2048,
"height": 1320,
"theme": "dark",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1819,
"diagramWidth": 1769,
"viewBoxWidth": 1364,
"minimumProjectedNodeTextPx": 9,
"minimumProjectedNodeText": "可推理·可通关",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": false,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "dark",
"file": "gdd-concept-map.visual-check.2048x1320.dark.png"
}
],
"contactSheet": "gdd-concept-map.visual-check.html"
},
"sidecars": {
"receipt": "gdd-concept-map.visual-check.json",
"contactSheet": "gdd-concept-map.visual-check.html"
}
}
@@ -0,0 +1,60 @@
# Requirements Traceability Matrix — Dungeon Sweeper
> 生成:2026-09-12 · 源:`docs/architecture/architecture.md` §Technical Requirements Baseline(63 条)+ ADR-0001~0005 · 2026-09-15 雾格移除清理(a4-001/002 需求文本与载体同步现行文档)
> 门检查口径:**Foundation 层零缺口** = 每条 Foundation TR 都有明确裁决载体(专项 ADR,或经 design-review 门禁的 Approved GDD 契约 + 本架构文档章节),无未决项。
> 状态图例:✅ 已覆盖 · 🔶 待 ADR(非 Foundation,随对应系统开工前裁决)· ⏳ 随未设计系统
## 覆盖总览
| 层 | TR 数 | 专项 ADR 覆盖 | GDD 契约 + 架构文档覆盖 | 待裁决 |
|---|---|---|---|---|
| Foundation | 36 | ADR-0001(a3-004)· ADR-0004(d2-014/016)· ADR-0005(d2-010/gc-002) | 32 条(D2/A3/A4/B3 GDD 均 Approved + 架构文档所有权/API 节) | **0** ✅ |
| Core | 21 | ADR-0001(b1-001/002)· ADR-0002(—) | 19 条 | 2(a1-011/012 → ADR-0006) |
| Feature | 2 | — | b1 对账协议等 | 0(b1-010 文档承载) |
| Presentation / Platform | 4 | ADR-0002(gc-008) | gc-006/007 文档 | 2(gc-004/005 随 D1/B5) |
## Foundation 层逐条(36 条——零缺口核验)
| TR | 需求 | 裁决载体 |
|---|---|---|
| d2-001~003 | 单一事实源 / ConfigDB 单点 / schema 先行 10 表 | D2 GDD(Approved)+ architecture.md §Module Ownership |
| d2-004 | 进度键 + R1 floor 解析 | D2 GDD R1 + architecture.md §API Boundaries |
| d2-005/006 | RngService 封闭流 / R2 派生与黄金向量 | D2 GDD R2 + architecture.md 原则 3 |
| d2-007 | 校验双通道 + 夹具注入 | D2 GDD + ADR-0004(载体) |
| d2-008 | 加载期/运行期致命 | D2 GDD Rule 6 |
| d2-009 | D2 零 UI 依赖 | D2 GDD Rule 7 + ADR-0003(core 隔离)+ architecture.md 原则 1 |
| **d2-010** | SCHEMA_VERSION 版本化 | **ADR-0005** |
| d2-011 | debug override 链 | D2 GDD R3 |
| d2-012 | 热重载状态机 + reload gate | D2 GDD + B1 GDD(唯一调用方) |
| d2-013 | 诊断收集器 | D2 GDD 校验双通道节 |
| **d2-014** | 冒烟框架协议 | **ADR-0004** |
| **d2-015** | RNG 快照 = seed + 计数器 | **ADR-0005** |
| **d2-016** | 项目级静态扫描域 | **ADR-0004**(S1–S8) |
| a3-001~003/005~008 | AP/RAP 账本、仲裁、相位机、refresh、表承载、last_round_end | A3 GDD(Approved)+ architecture.md |
| **a3-004** | round_end 双发射源 | **ADR-0001**(round_end_board / round_end_stopped) |
| a4-001/002 | 变异框架(原雾行跨字段校验已随雾格移除) | A4 GDD(Approved)+ 现行《地形变异》 |
| a4-003/004 | 生成期布置执行归 A1 / 零特判 | A4 GDD + A1/A2 GDD |
| b3-001~006 | 统一容器 / 时限量纲 / 五 API / 三分支 / 快照 / 不 clamp | B3 GDD(Approved) |
## Core / Feature / Presentation 层(27 条)
| TR | 需求 | 载体 | 状态 |
|---|---|---|---|
| a1-001~010, a2-001~010, b2-001~004/006, b4-001~009, b1-003~009/011 | 各系统行为契约(详见 architecture.md §TR Baseline) | 各 GDD(Approved)+ architecture.md §Module Ownership / §API Boundaries;事件类(a2-002/003、b1-001/002)由 **ADR-0001** 承载 | ✅ |
| **a1-011** | 度量独立入口三函数 | 🔶 待 **ADR-0006**(生成性能与异步化——A1 OQ2/OQ3) | 🔶 |
| **a1-012** | F2c 成本闸 / 生成性能姿态 | 🔶 待 **ADR-0006**(Core 层,A1 开工前裁决;切片证据已背书同步方案) | 🔶 |
| **b2-005**(注:face 归属实为 b2 OQ1 项) | query_attack/ambush_grant_plan 两 face | 🔶 待 **ADR-0007**(单位 API face 归属——B2 OQ1/OQ5) | 🔶 |
| **gc-004** | 双舞台资产管线 | ⏳ B5 前美术测试 → ADR-0011 | ⏳ |
| **gc-005** | 视觉锚三硬约束 | 🔶 D1 GDD(未写——MVP 层最后一块;切片教训四条为其硬输入) | 🔶 |
| gc-006/007 | 性能预算 / 平台输入 | technical-preferences + ADR-0002(窗口基座) | ✅ |
| **gc-008** | 渲染器选择 | **ADR-0002** | ✅ |
## 待裁决 ADR 队列(全部非 Foundation)
| ADR | 议题 | 触发时机 |
|---|---|---|
| ADR-0006 | 生成性能与异步化(A1 OQ2/OQ3 + 度量独立入口确认) | A1 生产实现前 |
| ADR-0007 | B2/B1 两 face 归属 + HeroTable 单行校验落点 | B2 生产实现前 |
| ADR-0008 | 零伤害死局跨字段护栏落点 | D2 生产化前 |
| ADR-0009 | 切片机制正式化(轮内刷新 / RewardSupplyController / LevelTable / grant_node_active / clear_blast) | 正式化批 |
| ADR-0010~0013 | A1 熔断 / B5 舞台 / A5 显影 / D3 存档格式(契约已由 ADR-0005 锁定) | 各系统 GDD 立项时 |
+155
View File
@@ -0,0 +1,155 @@
# 代码规范(Dungeon Sweeper)
> 生效:2026-09-11 · 来源:用户在垂直切片评审中逐一指出的真实陋习与缺陷,全部成文。
> 每条规则附本项目真实反面案例(标注缺陷编号/日期)——规则不是抽象美德,是踩过的坑。
> 约束范围:`game/`(原 prototypes/ 垂直切片,2026-09-24 转正)代码同尺度;与 CLAUDE.md「硬性红线」节既有硬约束
> (数值表单点 / RNG 封闭流 / Core 零 UI / fail-fast / spec-AC 一致性)并行生效,冲突以用户裁决为准。
**写任何代码前必须通读本文件。**
---
## 1. 结构纪律——高内聚、低耦合
### 1.1 禁止上帝对象
一个类只承载一个职责。场景主脚本(main)= **组合根**:模式流转、信号接线、核心写调用的唯一出口;
渲染、文案拼装、状态装配一律拆分为独立视图/装配器类。单文件超过 ~400 行即触发审视。
> **反面案例(2026-09-11,用户:"一个工程师看了想吐")**:main.gd 随功能追加长到 ~1100 行——棋盘渲染、
> HUD、结算日志格式化、四种模态覆盖层、调整板、蒙特卡洛模拟器、关卡流程全部糊在一个类里。
> 已拆分为 9 个单一职责单元(views/ + hud_presenter),main 降到 433 行组合根。
### 1.2 一切功能以类封装,禁止裸函数堆
设计稿与实现同尺度:功能 = 类(状态 + 行为 + 信号),类是封装的最小交付单位。
工具性纯函数只允许挂在明确的宿主类下(静态装配器/工厂),不允许无主散落。
> **反面案例(2026-09-11,用户:"我看你都是函数的方式……没有对象封装吧")**:供给控制器的伪代码
> 以裸函数呈现被否;正式实现按"B4 私有组合件 RewardSupplyController"封装。
### 1.3 依赖单向,核心写调用收口
依赖方向唯一:`views →(信号)→ 组合根 →(写调用)→ core`。
视图零核心写调用(纯 setter / 信号外发优先);"读核心状态 → 拼展示文案"集中于装配器(presenter);
核心写接口(settle_batch / submit_window_selection / runner 推进)只允许在组合根出现。
### 1.4 命名 = 领域命名,禁止系统 ID 前缀
文件名与类名禁止 `a1_`/`b1_`/`c1_` 等系统 ID 前缀(`a1_board_generator.gd` → `board_generator.gd`)。
设计追溯不靠前缀:每个核心实现文件头注必须带一行
`# 实现 GDD:design/archive/gdd-2026-09-14/<id>-<slug>.md(系统 ID Xn)`(2026-09-14 起旧 GDD 体系封存于归档,头注指归档路径;对应新 systems 文档定稿后再改指 `design/gdd/systems/` 新文档);系统 ID 只作为文档与 registry 的追溯锚。
豁免:D2 schema 类与 autoload(本就领域命名);文档/夹具登记名(如 b4_golden)属文档层。
> **反面案例(2026-09-11,用户:"是没有代码规范吗")**:ID 前缀是跟随 GDD 文档名的事实约定,
> 从未成文;同目录 `config_db`/`rng_service` 无前缀与 A/B/C 系列并存——两套命名即规范缺失的铁证。
### 1.5 禁止事实约定
任何"大家默认这么做"的惯例必须成文进本文件(或 CLAUDE.md)才算存在。
写代码时发现未成文但必须遵守的约定 → 当场补条文,不许靠惯性延续。
---
## 2. 契约纪律——跨层/跨类调用的语义
### 2.1 信号与事件契约成文
每个跨类信号:参数语义、发送时点、消费方依赖,必须在发送方头注成文(参照 A2 三事件面先例)。
状态机的每次迁移路径都必须发出对应事件——**缺一条路径的事件 = 消费方永久等待 = 软锁**。
> **反面案例(playtest 缺陷②)**:A2 旗动作花完末点 AP 后相位翻 Waiting 但不发 round_end,
> B1 结算点依赖该事件 → 整局软锁。v1 冒烟没抓到是因为自动玩家不用旗——路径覆盖不全的代价。
### 2.2 跨层标识符语义单一且成文
位置(index)/ 句柄(handle)/ 键(key)是三种不同的东西。跨类传递标识符时,
传递方的语义必须与接收方契约逐字一致,禁止凭"当前恰好相等"传值。
> **反面案例(playtest 缺陷④)**:窗口选择契约 = 窗口条目位置,UI 却传库存数组句柄——
> 未消耗时二者巧合相等;消耗一件后错位 → 选中件不消耗、邻件被误耗。
### 2.3 模态覆盖层层级规则
覆盖层遮挡关系由场景树加入顺序决定。打开任何模态覆盖层时,必须显式隐藏会遮挡它的全屏层;
新增覆盖层必须登记其在 z 序中的位置,并提供"打开可见性"的场景断言。
> **反面案例(playtest 缺陷⑤)**:数值调整板已实现,但打开时被不透明的开始覆盖层完全遮住
> = "点击无反应"。功能存在 ≠ 功能可达。
### 2.4 状态字段语义显式
跨战斗/跨场景携带或恢复的状态字段,其语义来源必须显式(表值 / 明确入参),禁止用入参隐式定义。
> **反面案例**:B2 spawn 把 hero 的 max_hp 设为入参 hero_hp——跨战斗携带 HP 时上限被压扁,
> 恢复点/治疗包的满血守卫全部失效。
---
## 3. 测试纪律——"存在"必须证明"可达且正确"
### 3.1 场景级探针标配
每新增一个可交互 UI 区域(视图/覆盖层/面板),必须配套场景级 headless 探针:
真实实例化场景 → 驱动真实输入路径 → 断言可见性与状态。**只有 core 层 AC 通过不算有测试**
(GDScript 编译不查字典字段与时序,呈现层错误只在运行时暴露)。
### 3.2 覆盖层/面板必测"打开后可见"
凡模态覆盖层,探针必须断言:打开后自身 visible 且遮挡物已隐藏(缺陷⑤回归断言)。
### 3.3 缺陷修复必须沉淀回归断言
每个缺陷修复必须在探针或冒烟中留下能抓住该缺陷的断言(旗动作耗尽 AP → 必须进入第 2 轮;
选用道具 → 库存态必须变 Used)。没有回归断言的修复视为未完成。
### 3.4 提交前全量验证
交付前必须全绿:`smoke_all.gd` + 全部场景探针(ui_probe / ui_click_probe / ui_tune_probe)。
行为探针使用随机化玩家行为(旗/停手/刷新/无效点击)——自动玩家不做的动作就是测试盲区。
---
## 4. 语言纪律——GDScript 4.x 实战坑(本项目踩实)
1. **多行 lambda 内禁止嵌套 match / 复杂控制流**(解析不可靠,报"Expected expression for match
pattern")——信号处理体一律抽成命名方法 + `bind` 传状态。
2. **Dictionary 取值返回 Variant,无类型推断**——参与算术或传参前显式声明类型(`var x: float = ...`)。
3. **RichTextLabel:`append_text()` 不回写 `text` 属性**——读可见文本用 `get_parsed_text()`。
4. **lambda 捕获循环变量按创建时值**——循环内先落地局部变量再捕获,或用 `bind`。
5. **重命名脚本必须同步 `.uid` 文件**(资源身份稳定);新增 `class_name` 后必须跑一次编辑器扫描
刷新全局类缓存,否则 `--headless --script` 模式解析失败。
6. **`print` 格式占位符与实参类型必须匹配**(`%d` 配 String = 静默输出字面量)——工具输出错
等于没有输出。
---
## 5. 数值与汇报纪律
### 5.1 数值口径必须显式标注尺度
任何期望值/均值汇报必须写明口径:**每格 / 每板 / 每战 / 每关**。混用口径 = 误导
("可选类 3.32"不加"每板",会被读成每格期望 >1)。
### 5.2 调参先测量,改后复测
改任何玩法数值前,先用扫描工具产出基线(通关率/难度类读数走 `balance_verifier.py` 冒险段——单关直入口口径已退役,机制类读数走 `reward_expectation.gd` 等);
改完必须复测回填数据。禁止拍脑袋调参。
### 5.3 数值设计必须跨尺度推演
在单一尺度校准的数值,必须推演其在更大尺度的复合结果(每战合理 × 关内囤积 = 关卡灾难)。
> **反面案例(2026-09-11)**:可选类 25% 格子配比在单战斗尺度校准(击杀需求 18/54),
> ×"库存关内延续"× 5 张板复合后 = 每关 24.8 件、刀总伤 180 > Boss 130 HP——预警⑥在关卡尺度坐实。
### 5.4 用户拍板的实现零发挥
拍板范围外的细节一律不自行补全——以"待办问题"清单列出等裁决。
(反面案例:清图奖励被自行设计成入库刀,用户纠正为"一次性高额伤害不入库"。)
---
## 6. 流程纪律
1. **大改动三段走**:设计稿/伪代码 → 用户审阅 → 实现 → 修改记录(README + session-state)。实现不得先于审阅。
2. **实现批收尾双登记**:切片 README 修改记录 + `production/session-state/active.md` 条目,缺一不可。
3. **规范变更必须成文进本文件**——发现未成文约定、或用户裁决产生新规则,当场补条文。
4. **呈现层缺陷的教训回写 D1 GDD 设计输入**(信号载荷 schema / 场景级测试 / 时点状态可见性)。
---
## 附:缺陷-规范对照表(本项目真实案例,2026-09-11)
| 缺陷 | 现象 | 根因 | 对应规则 |
|---|---|---|---|
| ① UI 抖动/"第3轮"消失 | 悬停文案换行推挤侧栏,末行被挤出视口 | 动态内容参与布局回流 | 1.1 拆分 + 视图定长槽(HUDView) |
| ② Boss 战 AP 用完不结算(软锁) | 插旗耗尽末点 AP 后整局冻结 | 旗动作路径缺轮末事件 | 2.1 事件发射完整性 |
| ④ 道具使用后不消失 | 选用件留存、邻件被误耗 | 窗口位置 vs 库存句柄错位 | 2.2 标识符语义单一 |
| ⑤ 调整板点击无反应 | 面板已实现但被开始层遮挡 | 模态打开未隐藏遮挡物 | 2.3 层级规则 + 3.2 |
| 上帝对象 | main.gd ~1100 行 | 职责无边界持续堆积 | 1.1–1.3 |
| ID 前缀命名 | a1_/b1_ 与领域命名并存 | 事实约定未成文 | 1.4–1.5 |
| 可选类每关 24.8 件 | 速杀退化、囤刀秒 Boss | 单尺度校准未做跨尺度推演 | 5.3 |
+162
View File
@@ -0,0 +1,162 @@
# 文档写作规范(写文档前必读)
> **地位**:本文是本项目文档写作的规范权威,与 `docs/code-conventions.md`(代码规范)对等——写代码前必读代码规范,写文档前必读本文。CLAUDE.md 只保留导航指针,细则以本文为准。
> **适用范围**:`design/` 下一切现行文档(顶层文档、`systems/` 系统文档、`design/ux/`、`design/art/`、`design/research/`)与 `production/` 会话记录;`docs/` 其他说明文档参照执行。
> **成文**:2026-09-14。整合来源:用语纪律(2026-09-09 评审确立)· 文档形态纪律(2026-09-13 评审确立)· 两件套与树形文档体系纪律(2026-09-14 用户拍板)· 新骨架过渡口径(2026-09-14 用户拍板)· 可读性纪律(2026-09-14 用户提出)· 引用纪律(2026-09-14 用户提出)· 概念封装纪律(2026-09-15 用户提出)。
## 0. 总纲
文档是写给人看的:先让人读懂,再谈结构整齐。默认读者没有读过项目历史——可能是半年后的你自己,也可能是未来的协作者。下面所有规则都为这个读者服务。
## 1. 可读性第一(最高纪律)
**读者测试是每个小节的验收标准**:只读这一节,回答三个问题——
1. 这节管的是谁/什么?
2. 在什么情况下?
3. 会发生什么?
答不出来,就重写这一节,而不是给看不懂的地方补注释。
**与旧体系门禁的关系**:旧体系的 lean 格式六条硬规则与 `tools/gdd_lint.py` 门禁已随 2026-09-14 归档停用(PostToolUse 钩子同批停用,工具本体留 `tools/` 备查),此后仅用于解读 `design/archive/` 里的旧文档(解读时注意其表格占比口径为 30%,2026-09-14 由 60% 放宽)。现行文档不跑 lint;可读性是唯一的内容质量门。旧模板文件留档 `.claude/docs/templates/` 备查。
## 2. 白话写作规则
写文档像向朋友当面解释你的游戏。具体做法:
1. **完整句子优先**:用完整的句子把话说完;一段只说一件事,长句拆短句。
2. **禁止碎片笔记体**:正文不得写压缩碎片,不得把 `A→B→C` 箭头链当叙述(流程交给字符画,见第 3 节),不得使用只有作者自己懂的缩略语。
3. **每节人话开头**:每个小节的第一段先用白话说清"这是什么、为什么需要它",然后再展开细节。做到只读各节第一段,就能看懂整篇文档的故事线。
4. **术语首现即解释**:专业术语或项目概念第一次出现时,用一句话(可以用括号)解释它是什么,之后才允许直接使用。玩家会看到的专属名词(如"显影尘")同样要说清。
5. **叙述优先于引用**:AC-x、F-x 等编号引用不进正文叙述,只出现在验收标准与附录;正文用直白语言把规则本身说出来。
6. **反面基准**:数据库条目、会议纪要、只有评审工具能"读懂"的表格堆——都不是我们要的文档。
## 3. 表达分工:散文、表格、字符画、公式
不同内容用不同载体,各司其职:
| 内容 | 载体 | 纪律 |
|---|---|---|
| 规则的来龙去脉、行为逻辑 | 散文段 | 每段 ≤240 字,一段一件事 |
| 枚举、数值、边界、依赖 | 表格 | 表格只放可列举的事实;禁止把需要解释的内容塞进单元格让读者自己拼 |
| 流程、状态流转 | 字符画(代码块) | 禁止用散文逐步描述流程 |
| 公式 | 结论先行 | 先一行加粗结论,再放公式代码块、变量表与验算表;公式本体必须保留 |
| 体验锚点 | 关键词表 | 支柱/关键词/机制承诺 三列表,禁止长篇情绪描述 |
节点内顺序:先人话,再表格枚举,再字符流程图,最后公式举证。
**规则两段式**:核心规则先写"人话阶段"(作用于哪个系统、什么情况下、谁触发、触发后发生什么),再写"举证阶段"(逐效果给出计算方法或公式;纯流程规则明写"无计算")。规则名禁止纯名词短语(反面例:"获取路由")。
## 4. 用语纪律(禁自造黑话)
文档、评审输出、日志与一切沟通中**禁止自造黑话**——比喻性生造词、只有作者自己懂的缩略语(历史反例:"非雾腿""双闸")。
- 只能使用:(a)人人可懂的直白语言,或(b)游戏行业通用术语(如 flood fill、战争迷雾、连锁展开、结算、增益/减伤)。
- 需要指代新概念时,用直白的描述性短语(正例:"实测可推理率"——限定语与规范名绑定)。
- 确需给新概念命名时:首次出现处必须附一句白话定义,并登记 `design/registry` 术语条目——不得让读者猜测比喻含义。
- 唯一例外:玩家在游戏里也会看到的专属名词(道具名、界面用语,如"显影尘""探巢摆钟")。文档内部的机制概念不得造词。
## 5. 结构纪律
| 规则 | 内容 |
|---|---|
| 树形文档体系 | 全部设计文档构成一棵树;文档增删必须同步根索引 `design/gdd/02-系统清单.md` |
| 一页纸节点 | 一节、一条规则的内容控制在一页纸内(约 ≤80 行);装不下就拆节,禁止铺开 |
| 一系统一文档 | 每个子系统一份设计文档,不建 analysis 推演件;两三句能说清的"为什么"直接写进设计目的或规则正文 |
| 正文只写现行规则 | 禁止删除线、"(作废)"、"(日期+拍板)"式注记;修订只进文末「版本历史」表;义务台账、拍板记录等审计信息入 `design/registry` 或附录,不入正文 |
| 后置 ≠ 删除 | P1/P2 后置的内容逐条列出;复活后置项 = 修订文档,禁止静默加回 |
| 数值纪律 | 正文定规则、取值域、单位;平衡值进 `data/tables/`,provisional 必须标注;已锁数值事实源 = `data/tables/` 与 `design/registry/entities.yaml`。**强制收口条款(2026-09-17 数值压缩批后补成)**:① **双写失同步禁止**——任何数值变更(压缩 / 换装 / 投值 / 区间平移 / 增减行)必须同时同步所有引用该数值的 GDD 表格正文、registry `entities.yaml` 的 `mvp_content` / `value_status`、相关待办清单现状——三类之一未更新即视为该变更**未完成**(先例:2026-09-17 整体数值压缩批只动了 `.tres` 与切片代码,未同步 `地图战斗资源.md` / `增益状态.md` 正文表格、`entities.yaml` mvp_content、`06-待办清单.md` 的 `heal 负向直伤` 条目,造成文档滞后);② **调参表锁数后删现行值列**——数值已锁定的调参表必须删除「现行值」列(敌人系统 v0.6 先例),仅保留 schema 模板与取值域,指路存根指向 `data/tables/<file>.tres` 与 `registry/entities.yaml` 对应条目;③ **覆盖范围** = 奖励 / 状态 / 敌人 / 技能 / 充能 / 护甲等级 / 区间掷点 / 难度曲线 / D_ref 等所有数值字段;④ **维护通道** = 走「设计批 → 同步批」两段式——数值表改完不等于修订完成,须同步收口后方可记入版本历史;⑤ **压缩/换装批** = 一律走「`.tres` 落批 + 切片 smoke 跑通 + GDD 表格指路化 + registry mvp_content 指路化 + 待办清单现状刷新」五步全收才记版本号,缺步则版本号不增 |
## 6. 文档类型与骨架
**顶层文档**:
| 文档 | 管什么 |
|---|---|
| `00-游戏概念.md` | 一句话概念、目标受众、世界观命名、术语层级 |
| `01-核心循环.md` | 玩家醒来→做什么→得到什么→又想做什么,一条线讲清 |
| `02-系统清单.md` | 根索引:循环 × 子系统总表 + 文档树导航 |
| `03-内容规模.md` | 几个关卡、几种敌人、几种道具——做多少砍多少 |
| `04-美术音频方向.md` | 参考图、参考游戏、风格关键词 |
| `05-不做清单.md` | 明确写:这些我不要 |
| `06-待办清单.md` | 全项目待办事项的唯一登记处;各系统文档的「待办」节只留指路存根 |
**系统文档骨架**(`systems/` 下统一采用):
| 章节 | 要求 |
|---|---|
| 文首引言 | 一句话定位 + 状态(已定稿/范围草案/待设计)+ 旧版参考链接 |
| 设计目的 | 2-4 句白话,为什么需要这个系统 |
| 系统范围 | 负责/不负责两张表;表前一段白话说明边界 |
| 在核心循环中的位置 | 处于哪一段、上下游是谁(字符画一行 + 两句白话) |
| 核心规则 | 领域章节 ×2–6,每节一页纸 |
| 依赖与输出 | 上游/下游/对外事件与接口(表格;引用纪律见第 8 节) |
| P0 最小闭环 / P1·P2 后置 | MVP 最小集合与后置清单 |
| 调参表 | 参数名/取值域/现行值,provisional 标注 |
| 验收标准 | 可验证的判据 |
| 待办(可选) | 指路存根:条目单点登记 `06-待办清单.md`(见第 8 节「待办单点登记」),本节不再逐条登记 |
| 版本历史 | 表格 |
**命名**:中文、见名知意(如"游戏概念""棋盘生成");禁止 a1_/b1_ 编号前缀;不留纯空目录。正文中指代子系统一律用中文规范名;英文代号(供代码标识符与工具链)对照表见 `design/gdd/02-系统清单.md`,旧代号(a1–d6)仅限解读归档时使用,不得写进现行文档。
## 7. 概念封装纪律(概念对象化建模)
动笔之前先做概念建模:像写代码划分对象那样,把设计切成分界清晰的概念——每个概念有自己的规范名、自己的属性、自己的规则,概念之间靠显式关系(包含、挂载、引用)连接,而不是把所有内容揉进一个概念里。揉团的代价雾格事故已经付过一次(见第 8 节反例对照):概念没有边界,删除修改就得全仓翻找。
| 规则 | 内容 |
|---|---|
| 概念即对象 | 核心概念各自独立成体,有规范名、有属性、有规则边界;下笔前先能用一句"它是谁、它管什么"说清,说不清就是还没想完 |
| 属性归属唯一 | 每个属性只归一个概念——离开 A 就没有意义的属性写在 A 名下,禁止上提给容器、也不下压给成员 |
| 包含关系显式写 | 容器与成员的关系明文写出谁是容器、谁是成员、一对几(例:棋盘包含多个格);禁止让读者从散文里反推结构 |
| 概念状态用枚举 | 概念的类型与状态写成封闭枚举并列全成员(例:格的类型 = 安全格 / 雷格,格的状态 = 未开 / 已开);禁止用散文形容词零散罗列状态 |
| 附加物独立成对象 | 挂在概念上的附加内容若自己有一组属性或规则要写(例:格上的奖励/惩罚 = 格面资源),就是独立对象——父概念只保留挂载位(如"有无资源资格"),禁止把附加物摊平成父概念的一堆属性字段 |
| 关系不吸内容 | 概念间关系只写"有没有关系、是什么关系";对方的属性与规则留在对方名下——与第 8 节"引用不转述"同源 |
**切分正例**(现行排雷循环即按此切分):棋盘是地图级对象,包含多个格;格是成员对象,雷格/安全格是它的枚举属性;格上可能挂的奖励或惩罚(格面资源)是独立对象,本体与规则归地图战斗资源与战斗结算,格只保留"有无资源资格"一个挂载位。反面基准:把奖励的类型、数值、极性全摊开写成格子的属性字段——奖励规则一改,棋盘文档就得跟着改,这便是没封装。
**落盘载体**:概念对象登记 `design/registry/entities.yaml`(owner 与 referenced_by 由此机械维护);文档里概念怎么切,registry 就怎么登记,代码里的对象划分与之同构。
**与引用纪律的关系**:本节管单个概念的边界与归属(概念级封装),第 8 节管概念之间、文档之间的引用(跨文档解耦)——先有封装,第 8 节的单点定义、反向边与删除 SOP 才有干净的作用对象。
## 8. 引用纪律(跨文档高内聚低耦合)
目标:修改或删除一个概念时,只改它的 owner 文档;引用方最多删掉一个链接。反例对照 = 2026-09-14 雾格删除事故——雾格/迷雾/fog 三名并存、雾机制被织进多份文档的规则正文,导致全仓几十处文字修改与大量事后一致性复查。
| 规则 | 内容 |
|---|---|
| 单点定义 | 每个概念只在 owner 文档里定义一次,registry 术语条目登记其规范名 |
| 规范名唯一 | 正文只准用规范名,禁止别名散开——否则删除时 grep 搜不全 |
| 引用不转述 | 跨文档引用 = 规范名 + 链接,禁止复述对方的规则细节、公式与数值;若发现自己需要一条规则,那是自己的概念,就地定义 |
| 引用落到对外承诺 | 跨文档链接只锚到对方文档明示的对外内容(事件、接口、规则条目),不锚其内部细节 |
| 依赖逐条列出 | 系统文档的「依赖与输出」节逐条列出本系统依赖的他人概念(规范名 + 链接)——这份清单就是删除概念时的影响面清单 |
| 引用必登记反向边 | 引用他系统概念时,在被引方的 registry 条目 `referenced_by` 里登记——影响面靠它机械列出,不靠全仓搜 |
| 链接断了也好读 | 引用处的句子在链接失效后仍通顺完整(写"变异规则见《地形变异》",不把半截语义挂在链接里) |
| 待办单点登记 | 全项目待办事项单点登记于 `design/gdd/06-待办清单.md`:系统文档的「待办」节只留指路存根,不得自建条目表;新增待办条目直接登记清单,裁决后改「状态」列留痕、不删条目 |
**引用格式**:一律 Markdown 相对链接,指向文件、必要时带节锚点(例:`[地形变异 · 变异类型](systems/排雷循环/地形变异.md#变异类型)`)——机械可查,死链可被工具检出。
**删除概念 SOP**:① 查 registry 该条目的 `referenced_by`,得到影响面清单 → ② owner 文档删除概念 → ③ 纯引用方删链接即完事;机制被织入对方规则的,按清单逐处修订 → ④ 收尾跑一遍死链检查,全绿即干净。
**诚实边界**:机制耦合无法靠删链接消灭——别人的规则真的因你的概念而改变时,改内容不可避免。引用纪律保证的是:这类修改有一份现成清单(反向边 + 依赖节),不用事后全仓搜索。
## 9. 归档与事实源
- `design/archive/gdd-2026-09-14/` 是旧版 GDD 体系的只读归档:不维护、不修改,**禁止作为事实引用**。
- 旧文档里的概念写进现行文档时,必须翻译成现行术语与现行决策口径(例:雾格已从设计中移除,不得作为现行机制引用)。
- registry 中 owner 仍指向旧 GDD 路径的条目,在对应新系统文档定稿后逐条改指新路径。
## 10. 会话记录纪律
`production/session-state/active.md` 每条进度 ≤3 行(粗体键 + 短句);详情一律外链到对应文档或报告,禁止把定稿摘要、义务清单整段塞进条目。
## 版本历史
| 版本 | 日期 | 说明 |
|---|---|---|
| v1 | 2026-09-14 | 首版成文:整合用语纪律、文档形态纪律、两件套与树形文档体系、新骨架过渡口径,新增可读性第一纪律与系统文档骨架 |
| v2 | 2026-09-14 | 新增第 7 节引用纪律(跨文档高内聚低耦合:单点定义 / 规范名唯一 / 引用不转述 / 反向边登记 / 删除概念 SOP),原 7/8 节顺延为 8/9 |
| v3 | 2026-09-15 | 命名纪律补充:现行文档代号清偿完毕,子系统统一以中文规范名指代;英文代号对照表落盘 `design/gdd/02-系统清单.md`,旧代号仅限解读归档 |
| v4 | 2026-09-15 | 用语纪律正例去雾:第 4 节正例"剔除雾格后的可推理率"改为现行指标"实测可推理率"(原指标随雾格移除已删);规则文字与历史反例"非雾腿"、第 8 节雾格移除去向说明均不动 |
| v5 | 2026-09-15 | 新增第 7 节概念封装纪律(概念对象化建模:概念即对象 / 属性归属唯一 / 包含关系显式写 / 概念状态用枚举 / 附加物独立成对象 / 关系不吸内容),原 7/8/9 节顺延为 8/9/10,第 6 节骨架表与 CLAUDE.md 摘要的章节指针同步改指 |
| v6 | 2026-09-16 | 待拍板收编批:第 8 节引用纪律新增「待拍板单点登记」(待拍板事项单点登记 `design/gdd/06-待拍板清单.md`,系统文档只留指路存根,新增条目直接登记清单);第 6 节顶层文档表补入 `06-待拍板清单.md`,骨架表「待拍板」行改为指路存根口径 |
| v7 | 2026-09-16 | 06 清单改版(用户拍板):「待拍板」术语全仓改「待办」,清单更名 `design/gdd/06-待办清单.md`;清单内格式由四列表格改为「循环 → 系统 → 条目(四级标题)+ 粗体属性段(现状 / 归属 / 状态)」,条目解决后状态改"已解决(日期)"留痕;第 6 节顶层文档表与骨架表、第 8 节「待办单点登记」同步改指 |
@@ -0,0 +1,46 @@
# Godot — Version Reference
| Field | Value |
|-------|-------|
| **Engine Version** | 4.7.2-stable(2026-08-18 发布) |
| **Project Pinned** | 2026-09-07 |
| **LLM Knowledge Cutoff** | May 2025(训练数据覆盖 ~4.3/4.4) |
| **Risk Level** | **MEDIUM**(2026-09-12 降级)——4.5→4.7 API 级破坏性变更已逐条核实(源:官方迁移指南),无一条破坏本项目架构契约;残余风险 = 4.8(dev 阶段)未来升级 + 未覆盖的运行期边角行为,代理写码规则不变:不确定的 API 一律查 `docs.godotengine.org/en/4.7/` 或 WebSearch 核实后再用 |
| **Last Docs Verified** | 2026-09-12(4.5/4.6/4.7 迁移指南逐条核实 + 版本检查:4.7.2 仍为最新稳定版,4.8 仅 dev 快照) |
## Post-Cutoff Version Timeline
| Version | 发布 | 与本项目相关要点 |
|---|---|---|
| 4.4 | 2025-03(训练覆盖边缘) | — |
| 4.5-stable | 2025-09-15 | stencil 效果、无障碍改进;4.5.2(2026-03-19)为末版维护;**逐条变更已核实(2026-09-12)** |
| 4.6-stable | 2026-02 起(4.6.1 2026-02) | Modern 主题、flexible docks、编辑器 QoL;4.6.3 维护;**逐条变更已核实(2026-09-12)** |
| **4.7-stable** | 2026-06(**4.7.2 = 2026-08-18,本项目锁定**) | 见下;**逐条变更已核实(2026-09-12)** |
| 4.8 | dev 快照 1–4(开发中) | 无稳定版发布日期;稳定版发布后跑 `/setup-engine refresh` |
## 4.7 与本项目相关的变更(已核实)
- `Tween.tween_await()`(信号等待)——**不影响** B1"结算逻辑禁 await"约束(那是设计约束,非引擎限制)
- Control `offset_transform_*`(平移/旋转/缩放 Control 而不影响容器布局)——D1 HUD 可用
- DrawableTexture2D、AreaLight3D、HDR 输出、新 Asset Store——本项目暂不依赖
- 2D:CollisionShape2D 单向碰撞方向属性、conic gradient、AtlasTexture 进 TextureRect
- **GDScript 两条语义变化**(打包数组元素赋值不触发属性 setter;类型化返回覆盖必须显式 return)——详见 `current-best-practices.md`
- **新项目默认值已变**(stretch mode `canvas_items`、aspect `expand`;4.6 起 Windows 驱动 D3D12)——详见 `current-best-practices.md`
- Android/iOS/XR 增强——本项目 PC 先行,无关
## Migration Guides(升级/写码前必读)
- 4.7: https://docs.godotengine.org/en/4.7/tutorials/migrating/upgrading_to_godot_4.7.html
- 4.6 / 4.5 同路径模式:`.../tutorials/migrating/upgrading_to_godot_4.6.html`、`..._4.5.html`
- 三份指南的逐条中文对照与项目相关性标注见 `breaking-changes.md`(2026-09-12 版)
## Note
**MEDIUM 风险处置(2026-09-12 更新)**:4.5→4.7 的 API 级破坏性变更清单已逐条核实并入 `breaking-changes.md`,deprecated → replacement 对照已并入 `deprecated-apis.md`;对本项目的结论 = **无一破坏 GDD 已锁架构契约**。残余风险两项:① 4.8 未来升级(dev 阶段,稳定版后 refresh);② 运行期边角行为(迁移指南未覆盖的引擎内部行为差异)——代理写码规则保持不变:**不确定的 API 一律查 `docs.godotengine.org/en/4.7/` 或 WebSearch 核实后再用**。
## Verification History
| 日期 | 动作 |
|---|---|
| 2026-09-07 | 初次锁定 4.7.2;核实版本时间线与 4.7 特性清单 |
| 2026-09-12 | `/setup-engine refresh`:4.5/4.6/4.7 官方迁移指南逐条核实(breaking-changes.md 逐条清单 + deprecated-apis.md 对照表 + 新建 current-best-practices.md);版本检查确认 4.7.2 仍为最新稳定版(4.8 仅 dev);风险等级 HIGH → MEDIUM |
@@ -0,0 +1,115 @@
# Godot Breaking Changes(4.4 → 4.7)
> **Last verified: 2026-09-12**(官方迁移指南三份逐条核实——4.5/4.6/4.7 API 级清单已补全,原 2026-09-07"待 refresh"状态解除)
> 训练数据覆盖至 ~4.4:4.4 及以前的变更视为"已知基线",4.5+ 为超出截止的增量。
> 官方迁移指南(升级/写码前必读):
> - 4.5:https://docs.godotengine.org/en/4.7/tutorials/migrating/upgrading_to_godot_4.5.html
> - 4.6:https://docs.godotengine.org/en/4.7/tutorials/migrating/upgrading_to_godot_4.6.html
> - 4.7:https://docs.godotengine.org/en/4.7/tutorials/migrating/upgrading_to_godot_4.7.html
>
> 项目相关性图例:🔵 需要留意(本项目域内)/ ⚪ 本项目不用(3D/XR/网络/移动/GLTF/编辑器插件)。
> 本项目形态:2D 回合制、GDScript 单语言、Control 重 UI、autoload + .tres、无 3D/物理/网络。
## 4.5(2025-09-15)
### API 级变更
| 类/方法 | 变更 | GDScript 兼容 | 项目相关性 |
|---|---|---|---|
| `Resource.duplicate(true)` | 深复制**不再覆盖外部子资源**;旧行为需 `duplicate_deep(DEEP_DUPLICATE_ALL)` | 行为变更 | 🔵 D2 schema/表资源复制语义注意 |
| `Node.set_name` | 参数 `String` → `StringName` | 兼容 | 🔵 |
| `JSONRPC.set_scope` | 更名 `set_method` | ❌ | ⚪ |
| `Node.get_rpc_config` | 更名 `get_node_rpc_config` | ❌ | ⚪(RPC 不用) |
| `RenderingServer.instance_reset_physics_interpolation` | **移除** | ❌ | ⚪(3D 插值) |
| `RenderingServer.instance_set_interpolated` | **移除** | ❌ | ⚪ |
| TextServer/Font/CanvasItem/TextLine/TextParagraph 全部 draw 方法 | 新增可选 `oversampling` 参数 | 兼容 | 🔵 D1 自绘文字 |
| `TextServerExtension` 四个 draw 虚方法 | 同上签名变化 | ❌(全不兼容) | ⚪(不实现 TextServer 扩展) |
| `RichTextLabel.add_image`/`update_image` | `size_in_percent` → `width_in_percent` + `height_in_percent` | 兼容 | 🔵(低——富文本用图时) |
| C# `RenderingDevice.Features.Address` | 更名 `BufferDeviceAddress` | 仅 C# | ⚪(C# 不用) |
| GLTF Accessor/BufferView 十字段 | int32 → int64 / 枚举化 | 仅 C# | ⚪(GLTF 不用) |
### 行为变更
- **TileMapLayer 物理分块默认开启**;`get_coords_for_body_rid()` 返回值变化;需 4.4 精确行为设 `physics_quadrant_size=1` —— 🔵 若棋盘实现走 TileMapLayer(当前设计倾向自绘,未定)
- 3D 模型导入 skeleton 行为修复(新文件新行为;旧文件需改 Import Naming Version)—— ⚪
- Navigation 区域默认异步线程更新;navmesh 合并次序变化 —— ⚪(A2 用自研 BFS,不用 NavigationServer)
- Jolt:`areas_detect_static_bodies` 设置移除 —— ⚪
- `ProjectSettings.add_property_info()` 对缺失/非法键(含 `usage`)告警 —— 🔵(若程序化注册设置项)
## 4.6(2026-02)
### API 级变更
| 类/方法 | 变更 | GDScript 兼容 | 项目相关性 |
|---|---|---|---|
| `FileAccess.get_as_text` | `skip_cr` 参数**移除** | ❌ | 🔵 文本读取路径 |
| `FileAccess.create_temp` | `mode_flags` int → `ModeFlags` 枚举 | 兼容性检查变化 | 🔵 |
| AnimationPlayer `assigned_animation`/`autoplay`/`current_animation` | `String` → `StringName`(`get_queue()`、`current_animation_changed` 信号参数同步) | ❌(类型不兼容) | 🔵 B5 期 |
| SpringBoneSimulator3D 三个枚举 | 移至 `SkeletonModifier3D` | ❌ | ⚪ |
| StreamPeerTCP / TCPServer | `disconnect_from_host`/`get_status`/`poll`、`is_connection_available`/`is_listening`/`stop` 上移至 `StreamPeerSocket`/`SocketServer` 基类 | 兼容(继承) | ⚪(网络不用) |
| `Control.grab_focus`/`has_focus`、`LineEdit.edit`、`SplitContainer.clamp_split_offset`、`FileDialog.add_filter` | 新增可选参数 | 兼容 | 🔵 D1 |
| EditorFileDialog | `add_side_menu` **移除**;17 方法 / 8 属性 / 4 信号并入 `FileDialog` 基类 | 兼容(继承) | ⚪(编辑器) |
| `Performance.add_custom_monitor` | 新增可选 `type` 参数 | 兼容 | 🔵(调试遥测可选) |
### 行为 / 格式变更
- **TSCN 格式**:`load_steps` 不再写入、节点唯一 ID 入文件——双向兼容,但 VCS diff 变大(可 Project > Tools > Upgrade Project Files 预升级)—— 🔵
- **Glow 默认值大改**(blend mode Soft Light → Screen、intensity 0.8 → 0.3、各 level 重排);Mobile 渲染器 glow 重写 —— 🔵 D1/B5 用 2D Glow 时注意视觉基底已变
- AStar2D/AStar3D/AStarGrid2D:`from_id` 被禁用/固体时返回空路径 —— ⚪(自研 BFS)
- **新项目默认值**:Windows 渲染驱动 D3D12;3D 物理引擎 Jolt —— 🔵 创建 project.godot 时须知(本项目 2D、无 3D 物理,渲染驱动选择归实现期 ADR)
## 4.7(2026-06;4.7.2 = 本项目锁定版本)
### API 级变更
| 类/方法 | 变更 | GDScript 兼容 | 项目相关性 |
|---|---|---|---|
| **GDScript 语义 ①** | 打包数组元素赋值不再触发整属性 setter(GH-113228) | 语义变化 | 🔵 全部 .gd 代码 |
| **GDScript 语义 ②** | 覆盖类型化返回方法:返回类型自动继承,必须显式 `return`(GH-115763) | 语义变化 | 🔵 全部 .gd 代码 |
| `Object.is_class()` | 参数 `String` → `StringName` | 兼容 | 🔵 |
| `PhysicsServer2D.body_set_shape_as_one_way_collision` | 新增可选 `direction`(= CollisionShape2D 单向碰撞方向属性的 server 层) | 兼容 | 🔵(已核实特性的 API 面) |
| `PhysicsServer2DExtension._body_set_shape_as_one_way_collision` | 新增**必填** `direction` | ❌ | ⚪(不写 PhysicsServer 扩展) |
| `CanvasItem` 线条绘制 | 不再自动加抗锯齿羽化;粗线需手动绘制(GH-105122) | 行为变更 | 🔵 D1 自绘 |
| Input 设备 ID | 鼠标/键盘设备 ID `0` → `InputEvent.DEVICE_ID_MOUSE` / `DEVICE_ID_KEYBOARD` 常量 | 行为变更 | 🔵 D1 输入 |
| `Control.accessibility_live` | 枚举类型 `DisplayServer.` → `AccessibilityServer.AccessibilityLiveMode` | 仅 C# 不兼容 | 🔵(低) |
| RichTextLabel 图片参数 | `add_image`/`update_image` width/height int→float;`width_in_percent/height_in_percent` → `width_unit/height_unit`;常量 `UPDATE_WIDTH_IN_PERCENT` → `UPDATE_WIDTH_UNIT` | ❌(常量更名) | 🔵(低) |
| `Animation.length` | 元数据 float → double | 仅 C# 不兼容 | 🔵(低,B5 期) |
| AnimationNodeBlendSpace1D/2D | 布尔 `sync` 属性 → `SyncMode` 枚举(`sync_mode`) | 行为变更 | ⚪(3D 动画树) |
| `Font.find_variation` / `TreeItem.select` / `ZIPPacker.start_file` / `Image.save_exr`×2 | 新增可选参数 | 兼容 | ⚪/🔵 |
| `AudioEffectSpectrumAnalyzer.tap_back_pos` | **移除** | ❌ | ⚪(D4 Alpha 期若用频谱分析注意) |
| `EditorSceneFormatImporter` 七常量 | 并入 `ImportFlags` 枚举 | 仅 C# 源不兼容 | ⚪ |
| `EditorVCSInterface._commit` | 新增 `amend` 参数 | ❌ | ⚪ |
| `OptimizedTranslation.generate` | 返回 void → bool | 仅 C# 二进制 | ⚪ |
| `AudioStreamPlayer.area_mask` | 默认 1 → 0 | 行为变更 | ⚪(D4 Alpha 期注意) |
### 行为 / 默认值变更
- `LinearToSRGB` visual shader 不再 clamp 到 [0,1](Mobile/Forward+)—— 🔵(若写 shader)
- **新项目默认值**:stretch mode `disabled` → `canvas_items`、stretch aspect `keep` → `expand` —— 🔵 **创建 project.godot 时须知(训练数据默认值已过时)**
- macOS 最低版本 10.13 → 11 —— ⚪
- `LookAtModifier3D.relative` 默认 true → false —— ⚪
- `ResourceImporterDynamicFont.hinting` 默认 1 → 3 —— 🔵(字体导入)
- `rendering/reflections/sky_reflections/roughness_layers` 默认 7 → 8 —— ⚪(3D)
## 已知基线(≤4.4,训练数据内,低风险)
- 4.x 系列:Tween 一律经 `create_tween()` 获取(Tween 节点已废);信号连接用 `Callable` 语法;`@onready`/`@export` 注解体系;int 恒 64 位(D2 R2 规约依赖此语义,4.x 内稳定)
## 本项目影响评估(2026-09-12 复核)
| 变更 | 影响 |
|---|---|
| 4.7 全部已核实特性 | 均为增量,无破坏 D2/A3 已批准规约的项 |
| **4.5–4.7 逐条 API 变更** | **无一破坏 GDD 已锁架构契约**(autoload 体系 / .tres 单一事实源 / Callable 信号 / int64 二补数 / headless SceneTree 协议均不受影响) |
| 需入编码规范的项 | GDScript 两条新语义(打包数组 setter / 显式 return)→ `current-best-practices.md` |
| 需项目设置留意的项 | 新项目默认值已变(stretch mode / aspect / D3D12)→ `current-best-practices.md` |
| `Resource.duplicate(true)` 语义收窄 | D2 表资源含外部子资源时的复制语义注意 |
## Pending
- [x] 4.4→4.5 breaking changes 逐条清单(2026-09-12 官方迁移指南)
- [x] 4.5→4.6 breaking changes 逐条清单(2026-09-12 官方迁移指南)
- [x] 4.6→4.7 breaking changes 逐条清单(2026-09-12 官方迁移指南)
- [x] 各版本 deprecated → replacement 对照表(见 `deprecated-apis.md`,2026-09-12)
- [ ] 4.8 逐条清单(当前仅 dev 快照;稳定版发布后跑 `/setup-engine refresh`)
- [ ] modules/ 分域参考(按实现期需要建,非阻塞)
@@ -0,0 +1,49 @@
# Godot 当前最佳实践(4.7 视角,post-cutoff)
> **Last verified: 2026-09-12**(源:官方迁移指南 4.5/4.6/4.7 逐条核实 + 4.7 release 页)
> 用途:训练数据(~4.4)之后的实践差异速查。写码代理在以下事项上**不要**按训练数据默认值行事。
## 1. 新建 project.godot 的默认值差异(最易踩坑)
| 设置 | 训练数据默认(≤4.4) | 4.7 实际新项目默认 | 生效版本 |
|---|---|---|---|
| `display/window/stretch/mode` | `disabled` | `canvas_items` | 4.7(新项目) |
| `display/window/stretch/aspect` | `keep` | `expand` | 4.7(新项目) |
| `rendering/rendering_device/driver.windows` | Vulkan(Forward+) | D3D12 | 4.6(新项目) |
| `physics/3d/physics_engine` | GodotPhysics3D | Jolt | 4.6(新项目;本项目 2D、无 3D 物理,无关) |
本项目为 2D 棋盘 UI:stretch mode / aspect 的最终选择归渲染器实现期 ADR,但须知道**引擎新项目默认值已不是训练数据里的那个**——新建工程后检查这三项。
## 2. GDScript 4.7 两条语义变化(建议入编码规范)
1. **打包数组元素赋值不再触发属性 setter**(4.7,GH-113228):`my_packed_array[i] = v` 不再调用所在对象的 property setter——依赖 setter 副作用的写法必须改为显式赋回 + 显式调用 setter。D2 schema 类若以 setter 做校验,注意此差异。
2. **类型化返回的方法覆盖必须显式 `return`**(4.7,GH-115763):覆盖带返回类型的方法时返回类型自动继承,缺少 return 直接报错(旧版隐式返回 null)——虚方法覆盖一律显式写 return。
## 3. 资源与文件
- `Resource.duplicate(true)`(4.5 起):深复制**只覆盖内部子资源**,不再覆盖外部子资源;需旧行为用 `duplicate_deep(DEEP_DUPLICATE_ALL)`。D2 表资源含外部子资源时注意复制语义。
- `FileAccess.get_as_text()` 的 `skip_cr` 参数已移除(4.6)——换行处理如需自定义须自行实现。
- TSCN 格式(4.6):`load_steps` 不再写入、节点唯一 ID 入文件——双向兼容,但 VCS diff 会变大(可 Project > Tools > Upgrade Project Files 预升级,减少一次性大 diff)。
- `ProjectSettings.add_property_info()`(4.5):对缺失/非法键(含 `usage`)告警;改用 `set_as_basic()` / `set_restart_if_changed()` / `set_as_internal()`。
## 4. 2D / UI / 文本
- `CanvasItem` 线条绘制**不再自动加抗锯齿羽化**(4.7)——粗线/描边需手动方案(D1 自绘棋盘格边框时注意)。
- 输入设备 ID:比较输入来源时用 `InputEvent.DEVICE_ID_MOUSE` / `DEVICE_ID_KEYBOARD` 常量,**不要硬编码 0**(4.7)——D1 输入路由。
- 字体导入 `hinting` 默认 1 → 3(4.7);TextServer/Font/CanvasItem 全部 draw 方法新增可选 `oversampling` 参数(4.5)——自绘文字(数字标签)可显式控制过采样。
- RichTextLabel 图片参数两轮变化:4.5 `size_in_percent` → `width_in_percent`+`height_in_percent`;4.7 → `width_unit`/`height_unit`(bool → `ImageUnit`),常量 `UPDATE_WIDTH_IN_PERCENT` → `UPDATE_WIDTH_UNIT`。
- **Glow 默认值大改**(4.6):blend mode Soft Light → Screen、intensity 0.8 → 0.3、各 level 重排——2D 后处理发光的视觉基底已变,调参以 4.7 默认为起点。
- `Control.grab_focus` / `has_focus` 新增可选参数(4.6,`hide_focus` / `ignore_hidden_focus`)——焦点可视化管理更细。
## 5. 4.7 已核实新特性(本项目可用的增量)
- `Tween.tween_await()`:信号等待语法糖——**注意** B1 结算逻辑禁 await 是设计约束(可回放步骤流),该 API 不改变该约束
- Control `offset_transform_*`:平移/旋转/缩放 Control 不影响容器布局——D1 HUD 局部动效可用
- CollisionShape2D 单向碰撞方向属性(+ `PhysicsServer2D.body_set_shape_as_one_way_collision` 的 `direction` 参数)——本项目棋盘非物理驱动,暂不依赖
- conic gradient、AtlasTexture 进 TextureRect——D1 贴图/着色可选
- DrawableTexture2D、AreaLight3D、HDR 输出、内置 VirtualJoystick、新 Asset Store——本项目暂不依赖
- `Font.find_variation` 扩展(palette_index / custom_colors)、`TreeItem.select` 扩展——低相关
## 6. 本项目不消费的域(相关变更可忽略)
3D 渲染与物理(Jolt 行为变化 / SoftBody / WorldBoundary / SpringBone / LookAtModifier)、XR/OpenXR、网络(StreamPeerTCP/TCPServer 方法上移、RPC 更名)、GLTF 导入(int64 字段)、移动端导出(Android 模板结构变化、.NET 9)、编辑器插件 API(ImportFlags / VCS / ExportPlatform)。
@@ -0,0 +1,51 @@
# Godot Deprecated APIs(Don't use X → Use Y)
> **Last verified: 2026-09-12**
> 4.5–4.7 移除/更名/签名变化对照已补全(源 = 官方迁移指南,2026-09-12 逐条核实)。仍不含 4.8(dev 阶段)。**不要向本表添加未经验证的条目。**
## 4.7 release 页核实
- 4.7 release 页未列出新的 API 弃用;完整清单以其迁移指南为准(已逐条核实,见下节)
## 4.5–4.7 移除 / 更名 / 签名变化对照(源 = 官方迁移指南)
| Don't use | Use | Since | GDScript 影响 | 项目相关性 |
|---|---|---|---|---|
| `RenderingServer.instance_reset_physics_interpolation()` | (无替代——API 移除) | 4.5 | ❌ 移除 | ⚪ |
| `RenderingServer.instance_set_interpolated()` | (无替代——API 移除) | 4.5 | ❌ 移除 | ⚪ |
| `JSONRPC.set_scope()` | `set_method()` | 4.5 | ❌ 更名 | ⚪ |
| `Node.get_rpc_config()` | `get_node_rpc_config()` | 4.5 | ❌ 更名 | ⚪ |
| `Node.set_name(name: String)` | `set_name(name: StringName)` | 4.5 | 兼容(自动转换) | 🔵 |
| `TextServerExtension` 四个 draw 虚方法 | 签名新增 `oversampling` | 4.5 | ❌(虚方法覆盖不兼容) | ⚪ |
| RichTextLabel `size_in_percent` 参数 | `width_in_percent` + `height_in_percent` | 4.5 | 兼容(参数拆分) | 🔵(低) |
| `FileAccess.get_as_text(skip_cr)` | `get_as_text()`(**skip_cr 参数移除**) | 4.6 | ❌ 参数移除 | 🔵 |
| `EditorFileDialog.add_side_menu()` | (无替代;EditorFileDialog 17 方法/8 属性/4 信号并入 `FileDialog`) | 4.6 | ❌ 移除 | ⚪ |
| AnimationPlayer `assigned_animation` / `autoplay` / `current_animation`(String) | `StringName`(含 `get_queue()`、`current_animation_changed` 参数) | 4.6 | ❌ 类型不兼容 | 🔵(B5 期) |
| SpringBoneSimulator3D 三个枚举 | `SkeletonModifier3D.BoneDirection` / `.RotationAxis` | 4.6 | ❌ | ⚪ |
| `AudioEffectSpectrumAnalyzer.tap_back_pos` | (无替代——属性移除) | 4.7 | ❌ 移除 | ⚪(D4 期注意) |
| AnimationNodeBlendSpace1D/2D `sync: bool` 属性 | `sync_mode: SyncMode` 枚举 | 4.7 | ❌ 属性类型变化 | ⚪ |
| RichTextLabel `ImageUpdateMask.UPDATE_WIDTH_IN_PERCENT` | `UPDATE_WIDTH_UNIT`;`width/height_in_percent` 参数 → `width/height_unit`(bool → `ImageUnit`) | 4.7 | ❌ 常量/参数更名 | 🔵(低) |
| `Object.is_class("X")`(String 字面量) | `is_class(&"X")`(StringName) | 4.7 | 兼容 | 🔵 |
| `EditorVCSInterface._commit()` | 签名新增 `amend` | 4.7 | ❌(虚方法覆盖不兼容) | ⚪ |
| `PhysicsServer2DExtension._body_set_shape_as_one_way_collision()` | 签名新增必填 `direction` | 4.7 | ❌(虚方法覆盖不兼容) | ⚪ |
| C# `RenderingDevice.Features.Address` | `BufferDeviceAddress` | 4.5 | 仅 C# | ⚪ |
| GLTF Accessor/BufferView 十字段(int) | int64 / 强类型枚举 | 4.5 | 仅 C# | ⚪ |
## 训练数据内基线(≤4.4,高置信,本项目相关)
| Don't use | Use | Since | 备注 |
|---|---|---|---|
| `Tween` 节点 / `create_tween()` 以外的补间获取 | `create_tween()` / `tween_property()` | 4.0 | Tween 节点已移除 |
| 旧式 `connect("sig", instance, "method")` | `signal.connect(callable)` | 4.0 | Callable 语法 |
| `onready var` | `@onready var` | 4.0 | 注解体系 |
| `export var` | `@export var` | 4.0 | |
| `RandomNumberGenerator` 自建实例(玩法侧) | `RngService.open_stream(stream)` | 本项目 | D2 Rule 5(项目级禁令,非引擎弃用) |
| `randomize()`(玩法代码) | 禁止——种子归 `RngService.set_master_seed()`(C2 调用) | 本项目 | D2 Rule 5 |
| `yield` | `await` | 4.0 | 4.7 新增 `Tween.tween_await()`;但 B1 结算逻辑禁 await(设计约束) |
## Pending
- [x] 4.5 deprecated → replacement 对照(2026-09-12)
- [x] 4.6 deprecated → replacement 对照(2026-09-12)
- [x] 4.7 deprecated → replacement 对照(2026-09-12)
- [ ] 4.8 对照(dev 阶段;稳定版发布后 `/setup-engine refresh`)