532 lines
18 KiB
Markdown
532 lines
18 KiB
Markdown
# 游戏设计器 UI 优化 实现计划
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** 游戏设计器输入框撑满面板、法术元素标签改 5 复选框 + 自定义框、所有枚举下拉统一「中文 English」双语显示。
|
||
|
||
**Architecture:** 中央改 `designer_ui.gd`(工厂加 `SIZE_EXPAND_FILL` + 新增共享元素词表常量);各 tab 改标签常量为双语并把 spell 元素标签换控件、resonance 元素 A/B 换下拉。存储全部走 index/key,数据文件格式与内容零改动。
|
||
|
||
**Tech Stack:** Godot 4.6 (Mono) / GDScript `@tool`;验证用 godot-mcp-pro(`validate_script` / `execute_editor_script` / `reload_project` / `get_editor_screenshot`)。
|
||
|
||
**Spec:** `docs_dev/specs/2026-07-22-designer-ui-optimize-design.md`
|
||
|
||
**验证说明(TDD 适配):** 编辑器 tab 无单元测试框架,沿用既有模式——每个改动文件用 `validate_script` 校验语法;最后用 `execute_editor_script` 对 `spells.json`/`resonance.json` 做回读往返断言(Task 6)作为主功能门。所有 MCP 步骤要求 Godot 编辑器开启且 `godot_mcp` 在线;若掉线,聚焦编辑器窗口后重试。
|
||
|
||
---
|
||
|
||
### Task 1: `designer_ui.gd` — 布局撑满 + 共享元素词表
|
||
|
||
**Files:**
|
||
- Modify: `addons/game_designer/designer_ui.gd`
|
||
|
||
- [ ] **Step 1: 用完整内容替换该文件**
|
||
|
||
将 `addons/game_designer/designer_ui.gd` 整体替换为(保留 TAB 缩进):
|
||
|
||
```gdscript
|
||
## 游戏设计器通用 UI 辅助(静态方法 + 共享常量,各标签页复用)
|
||
@tool
|
||
extends RefCounted
|
||
|
||
## 元素词表(spell_tab 复选框 / resonance_tab 元素下拉共用);label 双语显示,tag 存储,index 对齐
|
||
const ELEM_LABELS := ["火 Fire", "冰 Ice", "雷 Lightning", "水 Water", "毒 Poison"]
|
||
const ELEM_TAGS := ["tag:fire", "tag:ice", "tag:lightning", "tag:water", "tag:poison"]
|
||
|
||
static func spin(min_v: float, max_v: float, step: float, val: float) -> SpinBox:
|
||
var s := SpinBox.new()
|
||
s.min_value = min_v; s.max_value = max_v; s.step = step
|
||
s.value = val
|
||
s.custom_minimum_size = Vector2(80, 0)
|
||
s.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
return s
|
||
|
||
static func line(text: String, placeholder: String = "") -> LineEdit:
|
||
var le := LineEdit.new()
|
||
le.text = text
|
||
le.placeholder_text = placeholder
|
||
le.custom_minimum_size = Vector2(120, 0)
|
||
le.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
return le
|
||
|
||
static func opt(items: Array, selected: int) -> OptionButton:
|
||
var o := OptionButton.new()
|
||
for it in items:
|
||
o.add_item(str(it))
|
||
o.selected = clampi(selected, 0, items.size() - 1)
|
||
o.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
return o
|
||
|
||
static func header(text: String) -> Label:
|
||
var l := Label.new()
|
||
l.text = text
|
||
l.add_theme_font_size_override("font_size", 15)
|
||
l.modulate = Color(1.0, 0.85, 0.4)
|
||
return l
|
||
|
||
static func cell_label(text: String) -> Label:
|
||
var l := Label.new()
|
||
l.text = text
|
||
l.custom_minimum_size = Vector2(70, 0)
|
||
return l
|
||
|
||
static func btn(text: String, cb: Callable) -> Button:
|
||
var b := Button.new()
|
||
b.text = text
|
||
b.pressed.connect(cb)
|
||
return b
|
||
|
||
static func status_label() -> Label:
|
||
var l := Label.new()
|
||
l.add_theme_font_size_override("font_size", 11)
|
||
l.modulate = Color(0.6, 0.9, 0.6)
|
||
return l
|
||
|
||
static func set_status(lbl: Label, msg: String, is_err: bool = false) -> void:
|
||
if lbl:
|
||
lbl.text = msg
|
||
lbl.modulate = Color(1.0, 0.5, 0.5) if is_err else Color(0.6, 0.9, 0.6)
|
||
|
||
static func load_json(path: String):
|
||
if not FileAccess.file_exists(path):
|
||
return null
|
||
return JSON.parse_string(FileAccess.get_file_as_string(path))
|
||
|
||
static func save_json(path: String, data) -> bool:
|
||
DirAccess.make_dir_recursive_absolute("res://data")
|
||
var f := FileAccess.open(path, FileAccess.WRITE)
|
||
if f:
|
||
f.store_string(JSON.stringify(data, " "))
|
||
f.close()
|
||
return true
|
||
return false
|
||
```
|
||
|
||
- [ ] **Step 2: 校验**
|
||
|
||
调用 `mcp__godot-mcp-pro__validate_script`,`path = "res://addons/game_designer/designer_ui.gd"`。Expected: 通过。
|
||
|
||
- [ ] **Step 3: 提交**
|
||
|
||
```bash
|
||
git add addons/game_designer/designer_ui.gd
|
||
git commit -m "feat(designer): 输入框撑满面板 + 共享元素词表常量"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2: `spell_tab.gd` — 元素复选框 + 双语标签
|
||
|
||
**Files:**
|
||
- Modify: `addons/game_designer/spell_tab.gd`
|
||
|
||
(以下 6 处用 Edit 工具精确替换;保留 TAB 缩进。依赖 Task 1 的 `UI.ELEM_LABELS`/`UI.ELEM_TAGS`。)
|
||
|
||
- [ ] **Step 1: 常量改双语 + 新增 LOGIC_OP_LABELS**
|
||
|
||
old:
|
||
```gdscript
|
||
const TYPE_NAMES := ["ACTION", "MODIFIER", "TRIGGER", "LOGIC"]
|
||
const ACTION_KINDS := ["弹道", "zone 毒池", "summon 召唤"] # 0=默认(不写 action_kind) 1=zone 2=summon
|
||
const LOGIC_OPS := ["every_n_shots", "if_hp_below", "if_enemy_nearby", "loop"]
|
||
const DMG_LABELS := ["物理", "火", "冰", "雷", "毒", "奥术"] # index = damage_type 整数
|
||
const ST_LABELS := ["无", "燃烧", "冻结", "中毒", "潮湿", "沾油", "眩晕", "连击", "易伤"] # index = status 整数
|
||
```
|
||
new:
|
||
```gdscript
|
||
const TYPE_NAMES := ["动作 ACTION", "修饰 MODIFIER", "触发 TRIGGER", "逻辑 LOGIC"]
|
||
const ACTION_KINDS := ["弹道 Projectile", "毒池 Zone", "召唤 Summon"] # 0=默认(不写 action_kind) 1=zone 2=summon
|
||
const LOGIC_OPS := ["every_n_shots", "if_hp_below", "if_enemy_nearby", "loop"] # 存储 key,不变
|
||
const LOGIC_OP_LABELS := ["每 N 次 every_n_shots", "生命低于 if_hp_below", "附近有敌 if_enemy_nearby", "循环 loop"] # 下拉显示
|
||
const DMG_LABELS := ["物理 Physical", "火 Fire", "冰 Ice", "雷 Lightning", "毒 Poison", "奥术 Arcane"] # index = damage_type 整数
|
||
const ST_LABELS := ["无 None", "燃烧 Burn", "冻结 Freeze", "中毒 Poison", "潮湿 Wet", "沾油 Oily", "眩晕 Stun", "连击 Combo", "易伤 Vuln"] # index = status 整数
|
||
```
|
||
|
||
- [ ] **Step 2: 成员变量 —— 替换 `_tags`**
|
||
|
||
old:
|
||
```gdscript
|
||
var _tags: LineEdit
|
||
```
|
||
new:
|
||
```gdscript
|
||
var _elem_checks: Array = []
|
||
var _custom_tags: LineEdit
|
||
```
|
||
|
||
- [ ] **Step 3: `_ready()` —— 元素标签行换复选框 + 自定义框**
|
||
|
||
old:
|
||
```gdscript
|
||
_tags = _row("元素标签", UI.line("", "逗号分隔,如 tag:fire"))
|
||
```
|
||
new:
|
||
```gdscript
|
||
var erow := HBoxContainer.new()
|
||
erow.add_child(UI.cell_label("元素标签"))
|
||
_elem_checks.clear()
|
||
for i in UI.ELEM_LABELS.size():
|
||
var cb := CheckBox.new(); cb.text = UI.ELEM_LABELS[i]
|
||
_elem_checks.append(cb); erow.add_child(cb)
|
||
add_child(erow)
|
||
_custom_tags = _row("自定义标签", UI.line("", "非元素 tag,逗号分隔"))
|
||
```
|
||
|
||
- [ ] **Step 4: `_ready()` —— `_lop` 下拉改用双语显示数组**
|
||
|
||
old:
|
||
```gdscript
|
||
_lop = UI.opt(LOGIC_OPS, 0)
|
||
```
|
||
new:
|
||
```gdscript
|
||
_lop = UI.opt(LOGIC_OP_LABELS, 0)
|
||
```
|
||
|
||
- [ ] **Step 5: `_on_select()` —— 载入元素勾选 + 自定义**
|
||
|
||
old:
|
||
```gdscript
|
||
_tags.text = ",".join(d.get("element_tags", []))
|
||
```
|
||
new:
|
||
```gdscript
|
||
var etags: Array = d.get("element_tags", [])
|
||
for i in _elem_checks.size():
|
||
_elem_checks[i].button_pressed = UI.ELEM_TAGS[i] in etags
|
||
var custom_list := []
|
||
for t in etags:
|
||
if not (String(t) in UI.ELEM_TAGS): custom_list.append(String(t))
|
||
_custom_tags.text = ",".join(custom_list)
|
||
```
|
||
|
||
- [ ] **Step 6: `_on_apply()` —— 收集勾选 + 自定义**
|
||
|
||
old:
|
||
```gdscript
|
||
var tags: Array = []
|
||
for t in _tags.text.split(",", false):
|
||
var s := t.strip_edges()
|
||
if s != "": tags.append(s)
|
||
```
|
||
new:
|
||
```gdscript
|
||
var tags: Array = []
|
||
for i in _elem_checks.size():
|
||
if _elem_checks[i].button_pressed: tags.append(UI.ELEM_TAGS[i])
|
||
for t in _custom_tags.text.split(",", false):
|
||
var s := t.strip_edges()
|
||
if s != "" and not (s in tags): tags.append(s)
|
||
```
|
||
|
||
- [ ] **Step 7: 校验**
|
||
|
||
调用 `mcp__godot-mcp-pro__validate_script`,`path = "res://addons/game_designer/spell_tab.gd"`。Expected: 通过。
|
||
|
||
- [ ] **Step 8: 提交**
|
||
|
||
```bash
|
||
git add addons/game_designer/spell_tab.gd
|
||
git commit -m "feat(spell-editor): 元素标签改复选框 + 选项双语显示"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3: `resonance_tab.gd` — 双语 + 元素下拉
|
||
|
||
**Files:**
|
||
- Modify: `addons/game_designer/resonance_tab.gd`
|
||
|
||
- [ ] **Step 1: 常量 —— 新增 MATCH_LABELS**
|
||
|
||
old:
|
||
```gdscript
|
||
const MATCH = ["adjacent", "anywhere_in_deck"]
|
||
```
|
||
new:
|
||
```gdscript
|
||
const MATCH = ["adjacent", "anywhere_in_deck"] # 存储 key
|
||
const MATCH_LABELS = ["相邻 adjacent", "卡组内任意 anywhere_in_deck"]
|
||
```
|
||
|
||
- [ ] **Step 2: 成员变量 —— `_p0`/`_p1` 改 OptionButton**
|
||
|
||
old:
|
||
```gdscript
|
||
var _p0: LineEdit
|
||
var _p1: LineEdit
|
||
```
|
||
new:
|
||
```gdscript
|
||
var _p0: OptionButton
|
||
var _p1: OptionButton
|
||
```
|
||
|
||
(注意:这两行在文件里位于 `var` 声明区,缩进为单 TAB;若实际无前导 TAB,请按文件实际缩进匹配。)
|
||
|
||
- [ ] **Step 3: `_ready()` —— 元素 A/B 与匹配方式换下拉**
|
||
|
||
old:
|
||
```gdscript
|
||
_p0 = _row("元素 A", UI.line("", "tag:water"))
|
||
_p1 = _row("元素 B", UI.line("", "tag:lightning"))
|
||
_match = _row("匹配方式", UI.opt(MATCH, 0))
|
||
```
|
||
new:
|
||
```gdscript
|
||
_p0 = _row("元素 A", UI.opt(UI.ELEM_LABELS, 0))
|
||
_p1 = _row("元素 B", UI.opt(UI.ELEM_LABELS, 0))
|
||
_match = _row("匹配方式", UI.opt(MATCH_LABELS, 0))
|
||
```
|
||
|
||
- [ ] **Step 4: `_on_select()` —— 按 tag 选中元素**
|
||
|
||
old:
|
||
```gdscript
|
||
_p0.text = String(p[0]) if p.size() > 0 else ""
|
||
_p1.text = String(p[1]) if p.size() > 1 else ""
|
||
_match.selected = max(0, MATCH.find(String(d.get("match", "adjacent"))))
|
||
```
|
||
new:
|
||
```gdscript
|
||
_p0.selected = max(0, UI.ELEM_TAGS.find(String(p[0])) if p.size() > 0 else 0)
|
||
_p1.selected = max(0, UI.ELEM_TAGS.find(String(p[1])) if p.size() > 1 else 0)
|
||
_match.selected = max(0, MATCH.find(String(d.get("match", "adjacent"))))
|
||
```
|
||
|
||
- [ ] **Step 5: `_on_apply()` —— 写回 tag**
|
||
|
||
old:
|
||
```gdscript
|
||
"pattern": [_p0.text.strip_edges(), _p1.text.strip_edges()],
|
||
"match": MATCH[_match.selected],
|
||
```
|
||
new:
|
||
```gdscript
|
||
"pattern": [UI.ELEM_TAGS[_p0.selected], UI.ELEM_TAGS[_p1.selected]],
|
||
"match": MATCH[_match.selected],
|
||
```
|
||
|
||
- [ ] **Step 6: `_on_new()` —— 默认配方用合法元素**
|
||
|
||
old:
|
||
```gdscript
|
||
_data.append({"id": "new_recipe", "pattern": ["tag:a", "tag:b"],
|
||
```
|
||
new:
|
||
```gdscript
|
||
_data.append({"id": "new_recipe", "pattern": ["tag:water", "tag:lightning"],
|
||
```
|
||
|
||
- [ ] **Step 7: 校验**
|
||
|
||
调用 `mcp__godot-mcp-pro__validate_script`,`path = "res://addons/game_designer/resonance_tab.gd"`。Expected: 通过。
|
||
|
||
- [ ] **Step 8: 提交**
|
||
|
||
```bash
|
||
git add addons/game_designer/resonance_tab.gd
|
||
git commit -m "feat(designer): 共鸣配方元素 A/B 改下拉 + 匹配方式双语"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4: wave / core / status 标签双语归一
|
||
|
||
**Files:**
|
||
- Modify: `addons/game_designer/wave_tab.gd`
|
||
- Modify: `addons/game_designer/core_tab.gd`
|
||
- Modify: `addons/game_designer/status_tab.gd`
|
||
|
||
- [ ] **Step 1: `wave_tab.gd` 常量双语**
|
||
|
||
old:
|
||
```gdscript
|
||
const TYPE_NAMES = ["Basic", "Fast", "Armored", "Elite", "MiniBoss", "Boss"]
|
||
const BOSS_NAMES = ["无", "MiniBoss(4)", "Boss(5)"] # 映射到 boss 值 -1/4/5
|
||
```
|
||
new:
|
||
```gdscript
|
||
const TYPE_NAMES = ["杂兵 Basic", "快速 Fast", "护甲 Armored", "精英 Elite", "首领 MiniBoss", "终焉 Boss"]
|
||
const BOSS_NAMES = ["无 None", "首领 MiniBoss", "终焉 Boss"] # 映射到 boss 值 -1/4/5
|
||
```
|
||
|
||
- [ ] **Step 2: `core_tab.gd` 常量归一「中文 English」**
|
||
|
||
old:
|
||
```gdscript
|
||
const TOPO = ["LINEAR 线性", "MATRIX 矩阵", "CIRCUIT 电路"]
|
||
const FEAT = ["无", "PERSISTENT_MEMORY 记忆", "DUAL_STREAM 双流", "ALWAYS_CAST_LAST", "SHUFFLE_DECK", "INFINITE_SPELLS"]
|
||
```
|
||
new:
|
||
```gdscript
|
||
const TOPO = ["线性 LINEAR", "矩阵 MATRIX", "电路 CIRCUIT"]
|
||
const FEAT = ["无 None", "记忆 PERSISTENT_MEMORY", "双流 DUAL_STREAM", "末位施法 ALWAYS_CAST_LAST", "洗牌 SHUFFLE_DECK", "无限法术 INFINITE_SPELLS"]
|
||
```
|
||
|
||
- [ ] **Step 3: `status_tab.gd` 常量归一「中文 English」**
|
||
|
||
old:
|
||
```gdscript
|
||
const STACK = ["REFRESH 刷新", "INTENSITY 叠层", "INDEPENDENT 独立"]
|
||
```
|
||
new:
|
||
```gdscript
|
||
const STACK = ["刷新 REFRESH", "叠层 INTENSITY", "独立 INDEPENDENT"]
|
||
```
|
||
|
||
- [ ] **Step 4: 校验三个文件**
|
||
|
||
分别调用 `mcp__godot-mcp-pro__validate_script`:`res://addons/game_designer/wave_tab.gd`、`res://addons/game_designer/core_tab.gd`、`res://addons/game_designer/status_tab.gd`。Expected: 均通过。
|
||
|
||
- [ ] **Step 5: 提交**
|
||
|
||
```bash
|
||
git add addons/game_designer/wave_tab.gd addons/game_designer/core_tab.gd addons/game_designer/status_tab.gd
|
||
git commit -m "feat(designer): 波次/Core/状态 枚举标签归一为中英双语"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 5: 手册文档更新(元素标签复选框)
|
||
|
||
**Files:**
|
||
- Modify: `docs/handbook/06_spell_editor.md`
|
||
|
||
- [ ] **Step 1: 更新操作步骤中的元素标签说明**
|
||
|
||
在 `docs/handbook/06_spell_editor.md` 第 3 节,找到这一步(新增法术流程里的第 5 步):
|
||
```markdown
|
||
5. 填 **元素标签**(共鸣用,如 `tag:lightning`;多个用逗号)
|
||
```
|
||
替换为:
|
||
```markdown
|
||
5. 勾选 **元素标签**(火/冰/雷/水/毒 复选框,可多选;共鸣用);非元素标签填「自定义标签」框
|
||
```
|
||
|
||
- [ ] **Step 2: 更新界面示意图中的元素标签行**
|
||
|
||
在同文件第 2 节界面示意图里,找到:
|
||
```
|
||
ID(唯一)/ 类型[ACTION ▾]/ 显示名 / 描述 / 元素标签(逗号)
|
||
```
|
||
替换为:
|
||
```
|
||
ID(唯一)/ 类型[动作 ACTION ▾]/ 显示名 / 描述
|
||
元素标签:[✓]火 [ ]冰 [✓]雷 [ ]水 [ ]毒 + 自定义标签框
|
||
```
|
||
|
||
- [ ] **Step 3: 提交**
|
||
|
||
```bash
|
||
git add docs/handbook/06_spell_editor.md
|
||
git commit -m "docs(spell-editor): 元素标签改复选框说明"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 6: 全量验证(回读往返 + 目测)
|
||
|
||
**Files:**
|
||
- 无(MCP 验证,不落盘)
|
||
|
||
- [ ] **Step 1: 重载并查错**
|
||
|
||
调用 `mcp__godot-mcp-pro__reload_project`,再 `mcp__godot-mcp-pro__get_editor_errors`(max_lines 50)。Expected: 无与本次改动相关的报错。(可能残留早前一次性 `gdscript://` 脚本的 `eq` 解析错,属无关历史噪声。)
|
||
|
||
- [ ] **Step 2: 法术回读往返(含 element_tags 集合比较)**
|
||
|
||
调用 `mcp__godot-mcp-pro__execute_editor_script`,`code`:
|
||
```gdscript
|
||
var UIH = load("res://addons/game_designer/designer_ui.gd")
|
||
var tab = load("res://addons/game_designer/spell_tab.gd").new()
|
||
EditorInterface.get_base_control().add_child(tab)
|
||
var raw = JSON.parse_string(FileAccess.get_file_as_string("res://data/spells.json"))
|
||
var fails = []
|
||
for sid in raw:
|
||
var found = false
|
||
for i in tab._list.item_count:
|
||
if String(tab._list.get_item_metadata(i)) == sid:
|
||
tab._on_select(i); found = true; break
|
||
if not found:
|
||
fails.append(sid + ":未在列表"); continue
|
||
var m = tab._read_meta()
|
||
if not (m is Dictionary):
|
||
fails.append(sid + ":read_meta null"); continue
|
||
var orig = raw[sid]["meta"]
|
||
for k in orig:
|
||
if m.has(k):
|
||
var a = m[k]; var b = orig[k]
|
||
var eq = (absf(float(a) - float(b)) < 0.0001) if (a is float or b is float) else (a == b)
|
||
if not eq: fails.append("%s.meta.%s" % [sid, k])
|
||
for k in m:
|
||
if not orig.has(k): fails.append("%s:+meta.%s" % [sid, k])
|
||
# element_tags 作为集合比较
|
||
var et_orig = raw[sid].get("element_tags", [])
|
||
var et_new = []
|
||
for i in tab._elem_checks.size():
|
||
if tab._elem_checks[i].button_pressed: et_new.append(UIH.ELEM_TAGS[i])
|
||
for t in tab._custom_tags.text.split(",", false):
|
||
var s = t.strip_edges()
|
||
if s != "" and not (s in et_new): et_new.append(s)
|
||
for t in et_orig:
|
||
if not (String(t) in et_new): fails.append("%s:tag缺 %s" % [sid, str(t)])
|
||
for t in et_new:
|
||
if not (t in et_orig): fails.append("%s:tag多 %s" % [sid, str(t)])
|
||
_mcp_print("SPELL FAILS=%d %s" % [fails.size(), str(fails)])
|
||
tab.queue_free()
|
||
```
|
||
Expected: `SPELL FAILS=0 []`。若非 0,按报错定位并修相应文件后重跑。
|
||
|
||
- [ ] **Step 3: 共鸣回读往返**
|
||
|
||
调用 `mcp__godot-mcp-pro__execute_editor_script`,`code`:
|
||
```gdscript
|
||
var UIH = load("res://addons/game_designer/designer_ui.gd")
|
||
var tab = load("res://addons/game_designer/resonance_tab.gd").new()
|
||
EditorInterface.get_base_control().add_child(tab)
|
||
var raw = JSON.parse_string(FileAccess.get_file_as_string("res://data/resonance.json"))
|
||
var fails = []
|
||
for idx in raw.size():
|
||
tab._on_select(idx)
|
||
var orig = raw[idx]
|
||
var op = orig.get("pattern", ["", ""])
|
||
var np = [UIH.ELEM_TAGS[tab._p0.selected], UIH.ELEM_TAGS[tab._p1.selected]]
|
||
if String(op[0]) != np[0] or String(op[1]) != np[1]:
|
||
fails.append("%s:pattern %s->%s" % [str(orig.get("id","?")), str(op), str(np)])
|
||
var nm = tab.MATCH[tab._match.selected]
|
||
if String(orig.get("match","adjacent")) != nm:
|
||
fails.append("%s:match %s->%s" % [str(orig.get("id","?")), str(orig.get("match")), nm])
|
||
_mcp_print("RESONANCE FAILS=%d %s" % [fails.size(), str(fails)])
|
||
tab.queue_free()
|
||
```
|
||
Expected: `RESONANCE FAILS=0 []`。(前提:`resonance.json` 现有配方 pattern 均为元素 tag;若某条含非元素值会在此暴露。)
|
||
|
||
- [ ] **Step 4: 目测布局与双语**
|
||
|
||
调用 `mcp__godot-mcp-pro__get_editor_screenshot`(若底部面板未展开,此步可跳过并改由用户人工确认)。确认:输入框/下拉撑满面板宽、下拉显示中英双语、法术页元素标签为复选框行。记录观察结果。
|
||
|
||
- [ ] **Step 5: 无新增提交(本任务仅验证)**
|
||
|
||
若 Step 2/3 触发修复,则各自 `git add <改动文件> && git commit -m "fix(designer): 修正回读往返"`;否则本任务无提交。
|
||
|
||
---
|
||
|
||
## Self-Review
|
||
|
||
**Spec 覆盖:**
|
||
- §2 布局撑满 → Task 1(line/opt/spin 加 EXPAND_FILL)✅
|
||
- §3 共享元素词表 → Task 1(ELEM_LABELS/ELEM_TAGS)✅
|
||
- §4.1 元素复选框 + 自定义框 → Task 2 Step 2/3/5/6 ✅
|
||
- §4.2 spell 双语 + LOGIC_OP_LABELS → Task 2 Step 1/4 ✅
|
||
- §5 wave 双语 → Task 4 Step 1 ✅
|
||
- §6 resonance 双语 + 元素下拉 → Task 3 ✅
|
||
- §7 core/status 归一,enemy/balance 不动 → Task 4 Step 2/3(enemy/balance 无任务,符合)✅
|
||
- §9 验证 → Task 6 ✅
|
||
- 文档(元素标签复选框)→ Task 5 ✅
|
||
|
||
**占位符扫描:** 无 TBD/TODO;每步含精确 old/new 代码块或命令。✅
|
||
|
||
**类型/命名一致性:** `UI.ELEM_LABELS`/`UI.ELEM_TAGS`(Task 1 定义,Task 2/3/6 引用一致);`_elem_checks`/`_custom_tags`(Task 2 定义,Task 6 引用);`LOGIC_OP_LABELS`(Task 2 定义并用于 `_lop`);`MATCH_LABELS`(Task 3);resonance `_p0/_p1` 由 LineEdit→OptionButton 全程一致。回读脚本经 `tab.UI.ELEM_TAGS`/`tab.MATCH` 访问常量(GDScript 实例可经实例访问脚本常量)。✅
|
||
|
||
**已知取舍:** 多元素法术 `element_tags` 规整为「火冰雷水毒」固定序(集合语义,Task 6 按集合断言);resonance 非元素 pattern 会被下拉回退(现数据无此情况,Task 6 Step 3 会暴露)。
|