# Mana 完整化实现计划(递增蓝耗 + infinite_spells + 魔力虹吸 + UI + 平衡) > **For agentic workers:** 用 superpowers:subagent-driven-development(推荐)逐任务实现。步骤用 `- [ ]` 复选框。 **Goal:** 把 Mana MVP 补成可发布完整功能:递增蓝耗防机枪、infinite_spells 卖血续命、多来源魔力虹吸、HUD 打磨、平衡。 **Architecture:** 扣蓝统一走 `SpellEvaluator._charge_mana()`(热值乘算 + infinite 转 HP 代价);heat/leech 状态在 PlayerStats,配置在 balance.json(SettingsManager 加载);leech 换杖重算;HP 代价累加于 SpellContext、施法末结算(15% 上限)。 **Tech Stack:** Godot 4.6 GDScript · 纯 JSON 数据驱动 · 验证用 Godot MCP,无单元测试框架。 > **验证约定**:每任务 MCP 运行时断言。前置 Godot+MCP 连通。改码后 `reload_project`+`get_editor_errors`(0;新 autoload/新常量引用误报以 `play_scene` 运行时为准)+ `play_scene(main)` 后 `execute_game_script`;@tool 编辑器改动用 `execute_editor_script`+`CACHE_MODE_IGNORE` fresh-load。跑完 `stop_scene`。 --- ### Task 1: balance.json 配置 + SettingsManager getters **Files:** `data/balance.json`, `scripts/autoloads/settings_manager.gd` - [ ] **Step 1: balance.json 加配置** 在 `data/balance.json` 顶层对象里(`aether_thresholds` 之后、末尾 `}` 前)加两段(记得前一行补逗号): ```json "mana_heat": { "per_cast": 0.01, "decay_per_sec": 0.1, "max": 1.0 }, "infinite_spells": { "hp_per_shot": 1.0, "hp_cost_cap_ratio": 0.15 } ``` - [ ] **Step 2: SettingsManager 加载 + getters** 在 `settings_manager.gd` 的 `var _AETHER_THRESHOLDS: Array = []` 之后加: ```gdscript var _MANA_HEAT: Dictionary = {"per_cast": 0.01, "decay_per_sec": 0.1, "max": 1.0} var _INFINITE: Dictionary = {"hp_per_shot": 1.0, "hp_cost_cap_ratio": 0.15} ``` 在 `_load_balance()` 里 `if data.has("aether_thresholds") ...` 块之后加: ```gdscript if data.has("mana_heat") and data["mana_heat"] is Dictionary: for k in data["mana_heat"]: _MANA_HEAT[k] = data["mana_heat"][k] if data.has("infinite_spells") and data["infinite_spells"] is Dictionary: for k in data["infinite_spells"]: _INFINITE[k] = data["infinite_spells"][k] ``` 在 `aether_for_wave()` 之后加 getters: ```gdscript func mana_heat_per_cast() -> float: return float(_MANA_HEAT.get("per_cast", 0.01)) func mana_heat_decay() -> float: return float(_MANA_HEAT.get("decay_per_sec", 0.1)) func mana_heat_max() -> float: return float(_MANA_HEAT.get("max", 1.0)) func infinite_hp_per_shot() -> float: return float(_INFINITE.get("hp_per_shot", 1.0)) func hp_cost_cap_ratio() -> float: return float(_INFINITE.get("hp_cost_cap_ratio", 0.15)) ``` - [ ] **Step 3: 验证(MCP)** — `play_scene(main)`: ```gdscript _mcp_print("heat per=%s decay=%s max=%s | inf hp=%s cap=%s (expect 0.01/0.1/1/1/0.15)" % [str(SettingsManager.mana_heat_per_cast()), str(SettingsManager.mana_heat_decay()), str(SettingsManager.mana_heat_max()), str(SettingsManager.infinite_hp_per_shot()), str(SettingsManager.hp_cost_cap_ratio())]) ``` Expected: 0.01/0.1/1.0/1.0/0.15。 - [ ] **Step 4: Commit** ```bash git add data/balance.json scripts/autoloads/settings_manager.gd git commit -m "feat(mana): balance.json mana_heat/infinite_spells 配置 + SettingsManager getters" ``` --- ### Task 2: PlayerStats + SpellContext 新状态 **Files:** `scripts/autoloads/player_stats.gd`, `scripts/domain/spell_system/spell_context.gd` - [ ] **Step 1: PlayerStats 加字段** 在 `player_stats.gd` 的 `var mana_regen: float = 5.0` 之后加: ```gdscript var mana_heat: float = 0.0 # 递增蓝耗热值(0=无,1=蓝耗翻倍) var mana_leech: float = 0.0 # 击杀回蓝量(换杖/换牌重算) ``` - [ ] **Step 2: PlayerStats 加方法** 在 `regen_mana()` 之后加: ```gdscript ## 击杀回蓝(魔力虹吸) func gain_mana(n: float) -> void: if n <= 0.0: return mana = min(mana_max, mana + n) stats_changed.emit() ## 主动 HP 代价(infinite_spells / heavy_cost)——直接扣血,不走难度减伤 func spend_hp_cost(amount: float) -> void: if amount <= 0.0: return hp = max(0.0, hp - amount) stats_changed.emit() if hp <= 0.0: EventBus.emit(EventID.PLAYER_DIED, {}) ``` - [ ] **Step 3: PlayerStats 订阅击杀回蓝** `_ready()` 当前为: ```gdscript func _ready() -> void: EventBus.subscribe(EventID.PLAYER_DAMAGED, _on_player_damaged) ``` 改为: ```gdscript func _ready() -> void: EventBus.subscribe(EventID.PLAYER_DAMAGED, _on_player_damaged) EventBus.subscribe(EventID.ENEMY_KILLED, _on_enemy_killed) func _on_enemy_killed(_payload: Dictionary) -> void: gain_mana(mana_leech) ``` (`reset_for_run()` 已置 mana=mana_max;无需动 mana_heat/leech,它们由 PlayerManager/combat_manager 管。) - [ ] **Step 4: SpellContext 加 hp_cost_accum** `spell_context.gd`:在 `var registers: PackedFloat32Array = PackedFloat32Array()` 之后加: ```gdscript var hp_cost_accum: float = 0.0 # 本次施法累积 HP 代价(heavy_cost + infinite),施法末结算 ``` 在 `reset()` 里 `if stats: stats.reset() ...` 之后加: ```gdscript hp_cost_accum = 0.0 ``` - [ ] **Step 5: 验证(MCP)** ```gdscript PlayerStats.set_mana_pool(100.0, 5.0); PlayerStats.mana = 50.0 PlayerStats.mana_leech = 4.0 PlayerStats.gain_mana(PlayerStats.mana_leech) _mcp_print("gain_mana 4 -> mana=%s (expect 54)" % str(PlayerStats.mana)) PlayerStats.hp = 100.0; PlayerStats.hp_max = 100.0 PlayerStats.spend_hp_cost(11.0) _mcp_print("spend_hp 11 -> hp=%s (expect 89, 直接扣不减伤)" % str(PlayerStats.hp)) var c = SpellContext.new(); c.hp_cost_accum = 5.0; c.reset() _mcp_print("ctx reset -> hp_cost_accum=%s (expect 0)" % str(c.hp_cost_accum)) ``` Expected: 54 / 89 / 0。`get_editor_errors` 运行时无(play_scene 能跑)。 - [ ] **Step 6: Commit** ```bash git add scripts/autoloads/player_stats.gd scripts/domain/spell_system/spell_context.gd git commit -m "feat(mana): PlayerStats mana_heat/leech+gain_mana/spend_hp_cost+击杀回蓝;SpellContext hp_cost_accum" ``` --- ### Task 3: PlayerManager 每帧衰减 heat + 换杖清零 **Files:** `scripts/domain/player_manager.gd` - [ ] **Step 1: 每帧衰减** `_physics_process()` 当前首行是 `PlayerStats.regen_mana(delta)`。在其后加: ```gdscript PlayerStats.mana_heat = maxf(0.0, PlayerStats.mana_heat - SettingsManager.mana_heat_decay() * delta) ``` - [ ] **Step 2: 换杖清零 heat** `equip_wand()` 里 `PlayerStats.set_mana_pool(...)` 分支之后加(函数末尾): ```gdscript PlayerStats.mana_heat = 0.0 ``` - [ ] **Step 3: 验证(MCP,需战斗自动施法或手动)** ```gdscript PlayerStats.mana_heat = 0.5 _mcp_print("set heat 0.5") ``` 下一次调用(间隔真实时间,_physics_process 衰减): ```gdscript _mcp_print("heat after wait=%s (expect < 0.5,衰减中)" % str(PlayerStats.mana_heat)) ``` Expected: 第二次 < 0.5(趋向 0)。 - [ ] **Step 4: Commit** ```bash git add scripts/domain/player_manager.gd git commit -m "feat(mana): PlayerManager 每帧衰减 mana_heat + 换杖清零" ``` --- ### Task 4: SpellEvaluator — 扣蓝 helper(热值乘算 + infinite 扣血)+ heavy_cost + HP 结算 **Files:** `scripts/domain/spell_system/spell_evaluator.gd` - [ ] **Step 1: 加 `_charge_mana` helper** 在 `_apply_modifier` 函数之前(`## ── 内部方法` 区)加: ```gdscript ## 统一扣蓝:base_cost>0 时按热值乘算扣蓝;infinite Core 蓝空转累加 HP 代价并继续 ## 返回 true=可执行该节点;false=蓝不足且非 infinite → 停施 func _charge_mana(base_cost: float, ctx: SpellContext, infinite: bool, heat_mult: float) -> bool: if base_cost <= 0.0: return true if PlayerStats.spend_mana(base_cost * heat_mult): return true if infinite: ctx.hp_cost_accum += SettingsManager.infinite_hp_per_shot() return true return false ``` - [ ] **Step 2: execute_compiled — 计算 infinite/heat + 改门控 + 结算** 将 `execute_compiled` 从 `var ctx: SpellContext = _acquire_ctx(...)` 到 while 循环体,改为如下(新增 infinite/heat_mult/cast_fired,替换门控行 333-335,并把 loop/branch 调用加参数): ```gdscript var ctx: SpellContext = _acquire_ctx(caster_id, persistent) ctx.hp_cost_accum = 0.0 var infinite: bool = compiled.feature_tags == CoreFeatureTag.INFINITE_SPELLS var heat_mult: float = 1.0 + PlayerStats.mana_heat var cast_fired: bool = false var deck: SpellDeck = compiled.make_runtime_deck() var ops_count: int = 0 var max_ops: int = MAX_OPS_PER_CPU * 5 var actions_remaining: int = 1 while deck.has_next() and ops_count < max_ops: ops_count += 1 var node: SpellNode = deck.pop() var mana_cost: float = float(node.meta.get("mana_cost", 0.0)) if not _charge_mana(mana_cost, ctx, infinite, heat_mult): break # 蓝不足且非 infinite → 停施 if mana_cost > 0.0: cast_fired = true match node.type: SpellNode.SpellType.ACTION: _push_projectile(node, ctx, spawn_pos, -1) actions_remaining -= 1 if actions_remaining <= 0: break SpellNode.SpellType.MODIFIER: _apply_modifier(node, ctx) actions_remaining = 1 + ctx.stats.multicast_count SpellNode.SpellType.TRIGGER: _push_trigger(node, ctx, spawn_pos) actions_remaining -= 1 if actions_remaining <= 0: break SpellNode.SpellType.LOGIC: if node.meta.has("fork_branch_ids"): for bid in node.meta["fork_branch_ids"]: _run_branch_payload(int(bid), ctx, spawn_pos, infinite, heat_mult) elif String(node.meta.get("logic_op", "")) == "loop": ops_count = _run_logic_loop(node, ctx, spawn_pos, deck, ops_count, max_ops, infinite, heat_mult) break elif _eval_logic(node, ctx, spawn_pos) == _LOGIC_SKIP_REST: break ``` - [ ] **Step 3: execute_compiled — 施法末结算 HP + heat** 在 while 循环之后、`if ops_count >= max_ops:` 之前插入: ```gdscript if ctx.hp_cost_accum > 0.0: PlayerStats.spend_hp_cost(minf(ctx.hp_cost_accum, PlayerStats.hp_max * SettingsManager.hp_cost_cap_ratio())) if cast_fired: PlayerStats.mana_heat = minf(SettingsManager.mana_heat_max(), PlayerStats.mana_heat + SettingsManager.mana_heat_per_cast()) ``` - [ ] **Step 4: _apply_modifier — heavy_cost 累加** 在 `_apply_modifier` 末尾 `if meta.has("multicast"): ...` 之后加: ```gdscript if meta.has("heavy_cost"): ctx.hp_cost_accum += float(meta["heavy_cost"]) ``` - [ ] **Step 5: _run_logic_loop — 加参数 + 改门控** 签名改为: ```gdscript func _run_logic_loop(node: SpellNode, ctx: SpellContext, spawn_pos: Vector2, deck: SpellDeck, ops_count: int, max_ops: int, infinite: bool, heat_mult: float) -> int: ``` 把内部门控(`var n_cost ...` / `if n_cost > 0.0 and not PlayerStats.spend_mana(n_cost): return ops_count`)替换为: ```gdscript var n_cost: float = float(n.meta.get("mana_cost", 0.0)) if not _charge_mana(n_cost, ctx, infinite, heat_mult): return ops_count ``` - [ ] **Step 6: _run_branch_payload — 加参数 + 改门控 + 递归传参** 签名改为: ```gdscript func _run_branch_payload(payload_id: int, ctx: SpellContext, spawn_pos: Vector2, infinite: bool, heat_mult: float) -> void: ``` 门控替换为: ```gdscript var node_cost: float = float(node.meta.get("mana_cost", 0.0)) if not _charge_mana(node_cost, ctx, infinite, heat_mult): break ``` LOGIC 分支里的递归调用 `_run_branch_payload(int(bid), ctx, spawn_pos)` 改为 `_run_branch_payload(int(bid), ctx, spawn_pos, infinite, heat_mult)`。 > 不要改 `execute_sub`(on-hit 子荷载)——它保持不扣蓝。 - [ ] **Step 7: 验证(MCP)** ```gdscript var core = WandPreset.make_core_by_id("wand_basic") # 热值乘算:heat=0.5 时 spark(5) 实扣 7.5 PlayerStats.set_mana_pool(100.0, 5.0); PlayerStats.mana = 100.0; PlayerStats.mana_heat = 0.5 BulletManager.reset() SpellEvaluator.execute_compiled(SpellEvaluator.compile_wand(core, [SpellRegistry.get_spell("action_spark_bolt")]), 0, Vector2(0,0)) _mcp_print("[heat] mana=%s (expect 92.5=100-7.5), bullets=%d(1)" % [str(PlayerStats.mana), BulletManager.get_active_count()]) # 堆热:cast_fired 后 heat 上升 _mcp_print("[heat] mana_heat=%s (expect 0.51=0.5+0.01)" % str(PlayerStats.mana_heat)) # 回归:heat=0 行为不变 PlayerStats.mana_heat = 0.0; PlayerStats.mana = 100.0; BulletManager.reset() SpellEvaluator.execute_compiled(SpellEvaluator.compile_wand(core, [SpellRegistry.get_spell("modifier_damage_plus"), SpellRegistry.get_spell("action_spark_bolt")]), 0, Vector2(0,0)) _mcp_print("[regr] mana=%s(80) bullets=%d(1)" % [str(PlayerStats.mana), BulletManager.get_active_count()]) ``` Expected: mana≈92.5/1, heat 0.51, 回归 80/1。`get_editor_errors` 运行时无。 - [ ] **Step 8: Commit** ```bash git add scripts/domain/spell_system/spell_evaluator.gd git commit -m "feat(mana): _charge_mana 统一门控(热值乘算+infinite扣血)+heavy_cost+HP结算" ``` --- ### Task 5: 魔力虹吸运行时(CoreDefinition + WandPreset + combat_manager 重算) **Files:** `scripts/domain/spell_system/core_definition.gd`, `scripts/autoloads/wand_preset.gd`, `scripts/domain/combat/combat_manager.gd` - [ ] **Step 1: CoreDefinition 加字段** `core_definition.gd` 的 `@export var base_mana_regen: float = 5.0` 之后加: ```gdscript @export var base_mana_leech: float = 0.0 # 击杀回蓝基础值(可叠加卡组内 meta.mana_leech) ``` - [ ] **Step 2: WandPreset 读取** `wand_preset.gd` `_core_from_dict` 里 `c.base_mana_regen = float(d.get("mana_regen", 5.0))` 之后加: ```gdscript c.base_mana_leech = float(d.get("mana_leech", 0.0)) ``` - [ ] **Step 3: combat_manager 重算 leech** `combat_manager.gd` `_rebuild_wand()` 里,构建 `nodes` 之后、`_equipped_compiled = SpellEvaluator.compile_wand(...)` 之前(或函数末尾)加: ```gdscript var leech: float = _equipped_core.base_mana_leech if _equipped_core else 0.0 for n in nodes: if n != null: leech += float(n.meta.get("mana_leech", 0.0)) PlayerStats.mana_leech = leech ``` - [ ] **Step 4: 验证(MCP)** ```gdscript var core = WandPreset.make_core_by_id("wand_basic") core.base_mana_leech = 2.0 # 模拟 _rebuild_wand 的求和逻辑 var nodes = [SpellRegistry.get_spell("action_spark_bolt")] var leech = core.base_mana_leech for n in nodes: leech += float(n.meta.get("mana_leech", 0.0)) _mcp_print("core leech 2 + spark(0) = %s (expect 2.0)" % str(leech)) _mcp_print("wand_basic 默认 base_mana_leech=%s (expect 0)" % str(WandPreset.make_core_by_id("wand_basic").base_mana_leech)) ``` Expected: 2.0 / 0.0。(求和 + 击杀回蓝的端到端在 Task 10 战斗内验。) - [ ] **Step 5: Commit** ```bash git add scripts/domain/spell_system/core_definition.gd scripts/autoloads/wand_preset.gd scripts/domain/combat/combat_manager.gd git commit -m "feat(mana): 魔力虹吸多来源(Core base + 卡组 meta.mana_leech 求和,换杖重算)" ``` --- ### Task 6: 数据内容(cores.json wand_eternal / spells.json heavy_cost·mana_leech) **Files:** `data/cores.json`, `data/spells.json` - [ ] **Step 1: cores.json 加 wand_eternal + 续航 Core leech** 在 `data/cores.json` 的 `circuit_fork` 条目之后(末尾 `}` 前,补逗号)加: ```json "wand_eternal": { "display_name": "永恒法杖", "slot_count": 8, "topology": 0, "cpu_limit": 6, "cast_interval": 0.5, "feature_tags": 5, "mana_max": 40, "mana_regen": 4, "mana_leech": 2 } ``` (`feature_tags: 5` = INFINITE_SPELLS;低蓝逼卖血;自带 base_mana_leech 2 作续航向。其余 Core 不加 mana_leech = 默认 0。) - [ ] **Step 2: spells.json 加 heavy_cost + mana_leech 修正器** 在 `data/spells.json` 的 `modifier_pierce_plus` 条目之后加两条(补逗号,JSON 合法): ```json "modifier_heavy_cost": { "type": 1, "display_name": "Blood Sacrifice", "description": "鲜血献祭:伤害+50,但每次施法扣 10 HP。", "element_tags": [], "meta": { "damage_add": 50.0, "heavy_cost": 10, "mana_cost": 0, "shop_cost": 26 } }, "modifier_mana_leech": { "type": 1, "display_name": "Mana Leech", "description": "魔力虹吸:击杀回复 2 魔力(放入卡组即生效)。", "element_tags": [], "meta": { "mana_leech": 2, "mana_cost": 0, "shop_cost": 22 } } ``` - [ ] **Step 3: 验证(MCP)** ```gdscript var e = WandPreset.make_core_by_id("wand_eternal") _mcp_print("wand_eternal feat=%d(5) mana_max=%s(40) leech=%s(2)" % [e.feature_tags, str(e.base_mana_max), str(e.base_mana_leech)]) var h = SpellRegistry.get_spell("modifier_heavy_cost") var l = SpellRegistry.get_spell("modifier_mana_leech") _mcp_print("heavy_cost meta.heavy_cost=%s(10) dmg=%s(50) | mana_leech meta.mana_leech=%s(2)" % [str(h.meta.get("heavy_cost")), str(h.meta.get("damage_add")), str(l.meta.get("mana_leech"))]) ``` Expected: feat=5/40/2;heavy 10/50;leech 2。`get_editor_errors` 运行时无(JSON 合法)。 - [ ] **Step 4: Commit** ```bash git add data/cores.json data/spells.json git commit -m "feat(mana): wand_eternal(infinite)+modifier_heavy_cost/mana_leech 数据" ``` --- ### Task 7: Core 编辑器加 mana_leech 栏(编辑器可调) **Files:** `addons/game_designer/core_tab.gd` - [ ] **Step 1: 加成员 + 表单栏 + 读写** (a)成员区 `var _mana_regen: SpinBox` 之后加: ```gdscript var _mana_leech: SpinBox ``` (b)`_ready()` 里 `_mana_regen = _row("回蓝/秒(Mana Regen)", UI.spin(0, 100, 0.5, 5))` 之后加: ```gdscript _mana_leech = _row("击杀回蓝(Mana Leech)", UI.spin(0, 100, 0.5, 0)) ``` (c)`_on_select()` 里 `_mana_regen.value = float(d.get("mana_regen", 5))` 之后加: ```gdscript _mana_leech.value = float(d.get("mana_leech", 0)) ``` (d)`_on_apply()` 的字典里,`"mana_max": int(_mana_max.value), "mana_regen": _mana_regen.value,` 之后加: ```gdscript "mana_leech": _mana_leech.value, ``` (e)`_on_new()` 的默认字典里 `"mana_max": 100, "mana_regen": 5,` 之后加 `"mana_leech": 0,`。 - [ ] **Step 2: 验证(编辑器 MCP fresh-load 往返)** ```gdscript var Tab = ResourceLoader.load("res://addons/game_designer/core_tab.gd", "GDScript", ResourceLoader.CACHE_MODE_IGNORE) var t = Tab.new(); EditorInterface.get_base_control().add_child(t) for i in t._list.item_count: if String(t._list.get_item_metadata(i)) == "wand_eternal": t._on_select(i); break _mcp_print("wand_eternal 表单 leech=%s (expect 2)" % str(t._mana_leech.value)) t._on_apply() _mcp_print("apply 后 data.mana_leech=%s (expect 2 保留)" % str(t._data.get("wand_eternal", {}).get("mana_leech", "DROP"))) t.free() ``` Expected: 2 / 2。`get_editor_errors` 清 stale 后 0。 - [ ] **Step 3: Commit** ```bash git add addons/game_designer/core_tab.gd git commit -m "feat(mana): Core 编辑器加击杀回蓝(mana_leech)栏" ``` --- ### Task 8: HUD 打磨(蓝条数字 + 低蓝 + 过热)+ UI_MANA i18n **Files:** `scenes/main/combat_s2.gd`, `translations/*.po` - [ ] **Step 1: i18n UI_MANA(4 语言末尾追加)** zh_CN: `msgid "UI_MANA"` / `msgstr "蓝 %d/%d"`;zh_TW: `藍 %d/%d`;en: `MP %d/%d`;ja: `MP %d/%d`。(各文件加一条,前空行分隔。) - [ ] **Step 2: 蓝条叠数字 Label** `combat_s2.gd` 成员区 `var _mana_bar: ProgressBar = null` 之后加 `var _mana_lbl: Label = null`。 `_setup_hud()` 里创建 `_mana_bar` 并 `add_child` 之后加: ```gdscript _mana_lbl = Label.new() _mana_lbl.position = Vector2(200, 86) _mana_lbl.size = Vector2(170, 16) _mana_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _mana_lbl.add_theme_font_size_override("font_size", 11) _hud_layer.add_child(_mana_lbl) ``` - [ ] **Step 3: _refresh_hud 更新数字 + 低蓝 + 过热** `_refresh_hud()` 里现有 `if _mana_bar: _mana_bar.max_value = ...; _mana_bar.value = ...` 块替换为: ```gdscript if _mana_bar: _mana_bar.max_value = PlayerStats.mana_max _mana_bar.value = PlayerStats.mana var frac: float = PlayerStats.mana / maxf(1.0, PlayerStats.mana_max) var fill := _mana_bar.get_theme_stylebox("fill") as StyleBoxFlat if fill: if frac < 0.2: var a: float = 0.6 + 0.4 * absf(sin(float(Time.get_ticks_msec()) / 150.0)) fill.bg_color = Color(0.8, 0.2, 0.2, a) # 低蓝暗红闪烁 else: fill.bg_color = Color(0.3, 0.55, 1.0) # 正常蓝 # 过热:mana_heat>0 时整条泛红 _mana_bar.modulate = Color(1.0, 1.0 - clampf(PlayerStats.mana_heat, 0.0, 0.7), 1.0 - clampf(PlayerStats.mana_heat, 0.0, 0.7)) if _mana_lbl: _mana_lbl.text = tr("UI_MANA") % [int(PlayerStats.mana), int(PlayerStats.mana_max)] ``` - [ ] **Step 4: 验证(MCP,进战斗 + 截图)** `play_scene(main)`→`SceneManager.start_new_game()`;下一调用: ```gdscript var cs = get_tree().current_scene _mcp_print("mana_lbl exists=%s" % str(cs._mana_lbl != null)) PlayerStats.mana = PlayerStats.mana_max * 0.1 # 低蓝 PlayerStats.mana_heat = 0.5 # 过热 _mcp_print("set low mana + heat; label=%s" % cs._mana_lbl.text) ``` `get_game_screenshot` 确认蓝条有数字、低蓝暗红、泛红。Expected: label 类似 "蓝 10/100"。 - [ ] **Step 5: Commit** ```bash git add scenes/main/combat_s2.gd translations/zh_CN.po translations/zh_TW.po translations/en.po translations/ja.po git commit -m "feat(mana): HUD 蓝条数字+低蓝暗红+过热泛红 + UI_MANA 键" ``` --- ### Task 9: 平衡调数值(基础杖续航) **Files:** `data/cores.json`, `data/spells.json` - [ ] **Step 1: 提高基础回蓝 + 降 spark 蓝耗** `data/cores.json`:`wand_basic` 的 `"mana_regen": 5` 改为 `"mana_regen": 8`;`wand_memory`/`matrix_board`/`circuit_fork` 的 `mana_regen` 相应 +3(保持相对关系:memory 5→8、matrix 6→9、circuit 5→8;wand_fast 4→6)。 `data/spells.json`:`action_spark_bolt` 的 `"mana_cost": 5` 改为 `"mana_cost": 3`。 - [ ] **Step 2: 验证(MCP 手感 + 数据)** ```gdscript _mcp_print("basic regen=%s(8) spark cost=%s(3)" % [str(WandPreset.make_core_by_id("wand_basic").base_mana_regen), str(SpellRegistry.get_spell("action_spark_bolt").meta.get("mana_cost"))]) ``` 起战斗观测:基础杖只放 spark 时蓝条应稳定不见底(cost3×2/s=6 vs regen8 → 净回);连打贵法术/堆射速仍会憋蓝。用 `get_game_screenshot` 记录蓝条状态。Expected: regen=8 / cost=3;战斗中基础突突不空蓝。 - [ ] **Step 3: Commit** ```bash git add data/cores.json data/spells.json git commit -m "balance(mana): 提高基础回蓝(→8)+降 spark 蓝耗(→3),基础杖能持续突突" ``` --- ### Task 10: 更新文档 callout **Files:** `docs/design/game_design.md`, `docs/design/numerical_design.md` - [ ] **Step 1: game_design §4.1 callout** 把 §4.1 现有 Mana callout 的「暂缺」句改为反映已实现:将其中 `**暂缺**:递增蓝耗、\`infinite_spells\` 扣血、全局+法杖取 Min、cast_delay。` 替换为 `**已补全**:递增蓝耗、\`infinite_spells\` 扣血、魔力虹吸(多来源)、HUD 打磨。**仍暂缺**:全局+法杖取 Min、cast_delay。`。 - [ ] **Step 2: numerical_design §5.A callout** 在 §5.A「递增蓝耗」条目末尾加一行: ```markdown > **⚠️ 实现现状 (2026-07-21 → 已实现)**:递增蓝耗已落地(`PlayerStats.mana_heat`,`balance.json.mana_heat` 可调,平衡页签);未做「最小帧限制/过热强制冷却」(用递增蓝耗替代)。 ``` - [ ] **Step 3: Commit** ```bash git add docs/design/game_design.md docs/design/numerical_design.md git commit -m "docs(mana): §4.1/§5.A 现状更新为 Mana 完整化已实现" ``` --- ### Task 11: 全量回归验收 **Files:** 无(纯验证) - [ ] **Step 1: 重载 + 0 错误**:`clear_output`→`reload_project`→`get_editor_errors`=0。 - [ ] **Step 2: 各机制 + 端到端**(`play_scene(main)`): ```gdscript var core = WandPreset.make_core_by_id("wand_basic") # 递增蓝耗 + 回归 PlayerStats.set_mana_pool(100,8); PlayerStats.mana=100; PlayerStats.mana_heat=0.5 BulletManager.reset(); SpellEvaluator.execute_compiled(SpellEvaluator.compile_wand(core,[SpellRegistry.get_spell("action_spark_bolt")]),0,Vector2(0,0)) _mcp_print("[heat] mana=%s (expect 95.5 = 100 - spark3 * heat1.5) bullets=%d(1)" % [str(PlayerStats.mana), BulletManager.get_active_count()]) # infinite 扣血 var e = WandPreset.make_core_by_id("wand_eternal"); PlayerStats.set_mana_pool(e.base_mana_max, e.base_mana_regen); PlayerStats.mana=0; PlayerStats.hp=100; PlayerStats.hp_max=100; PlayerStats.mana_heat=0 BulletManager.reset(); SpellEvaluator.execute_compiled(SpellEvaluator.compile_wand(e,[SpellRegistry.get_spell("action_spark_bolt")]),0,Vector2(0,0)) _mcp_print("[inf] 蓝空施法 hp=%s(<100 扣血) bullets=%d(1)" % [str(PlayerStats.hp), BulletManager.get_active_count()]) # heavy_cost + 15%上限 PlayerStats.hp=100; PlayerStats.mana=100; PlayerStats.mana_heat=0 BulletManager.reset(); SpellEvaluator.execute_compiled(SpellEvaluator.compile_wand(core,[SpellRegistry.get_spell("modifier_heavy_cost"),SpellRegistry.get_spell("action_spark_bolt")]),0,Vector2(0,0)) _mcp_print("[heavy] hp=%s(90=100-10) bullets=%d(1)" % [str(PlayerStats.hp), BulletManager.get_active_count()]) # 持久内存回归 var mc = WandPreset.make_core_by_id("wand_memory"); var cA=SpellEvaluator.compile_wand(mc,[SpellRegistry.get_spell("logic_every_n_shots"),SpellRegistry.get_spell("action_spark_bolt")]) var r=[]; PlayerStats.mana_heat=0 for i in 4: PlayerStats.mana=100; BulletManager.reset(); SpellEvaluator.execute_compiled(cA,0,Vector2(0,0)); r.append(BulletManager.get_active_count()) _mcp_print("[regr] every_n=%s (expect [0,0,1,0])" % str(r)) ``` Expected: 各机制符合注释;回归 [0,0,1,0]。 - [ ] **Step 3: 击杀回蓝 + UI(战斗内)**:`start_new_game`,装 wand_eternal 或塞 mana_leech 卡,杀怪观察 mana 回升;截图 HUD。 - [ ] **Step 4: DoD 核对**(spec §10)。`stop_scene`。 - [ ] **Step 5: Commit** ```bash git commit --allow-empty -m "chore(mana): 完整化验收通过(Godot 4.6 运行时实测各机制+回归全绿)" ``` --- ## 自检结论 - **Spec 覆盖**:P1→T1/T2/T3/T4;P2→T1/T2/T4/T6;P3→T2/T5/T6/T7;P4→T8;P5→T9;docs→T10;测试→各任务+T11。全覆盖。 - **无占位**:每步精确文件/完整代码/期望值。 - **类型一致**:`PlayerStats.mana_heat/mana_leech`+`gain_mana/spend_hp_cost`、`SpellContext.hp_cost_accum`、`SpellEvaluator._charge_mana(base_cost,ctx,infinite,heat_mult)`+`_run_logic_loop/_run_branch_payload` 加 `infinite,heat_mult`、`CoreDefinition.base_mana_leech`、`SettingsManager.mana_heat_*/infinite_*/hp_cost_cap_ratio`、`balance.json.mana_heat/infinite_spells`、`cores.json.mana_leech`、`spells.json meta.heavy_cost/mana_leech` 全程一致。