diff --git a/docs_dev/specs/2026-07-21-meta-progression-mvp-design.md b/docs_dev/specs/2026-07-21-meta-progression-mvp-design.md new file mode 100644 index 0000000..f0a973c --- /dev/null +++ b/docs_dev/specs/2026-07-21-meta-progression-mvp-design.md @@ -0,0 +1,142 @@ +# 元进展 MVP(碎片 + 解锁树 · 法术only)设计规范 + +> **日期**:2026-07-21 · **状态**:设计已确认,待写实现计划 +> **背景**:`game_design §7` 的元进展(以太碎片 / 解锁树 / 图鉴 / 成就)代码 0% 实现。本规范定义元进展的**第一个子项目**:碎片货币 + 法术解锁树的完整赚→花→门控闭环。图鉴、成就、核心解锁、挑战模式为后续子项目。 + +--- + +## 1. 目标与范围 + +**目标**:给游戏加入 Roguelite 长期粘性闭环——每局结束按波次获得**以太碎片**(全局持久、死亡不失),在主菜单花碎片**解锁进阶法术**,解锁后的法术进入商店掉落池。即使失败也有收获感。 + +**本次 MVP 包含**: +- 全局持久的碎片货币(`user://meta.json`)。 +- 每局结束(死亡)按到达波次发放碎片。 +- 主菜单「🔓 解锁」面板:显示碎片、花碎片解锁锁定法术。 +- 内容门控:~6 张进阶法术**开局锁定**,其余开局可用;商店池只纳入"开局解锁 + 已解锁"的法术。 +- ST-03「清除所有本地数据」一并清空 meta.json。 + +**明确不做(后续子项目/迭代)**: +- 核心(Core)解锁(依赖未实现的商店货架 B)、挑战模式解锁。 +- 图鉴 Codex、成就 / 炼金日志。 +- 通关首胜额外奖励(通关流程本身未实现)。 +- 存档防篡改(单机本地存档,MVP 用明文 JSON;EndlessRecords 的 HMAC 不引入此处)。 + +--- + +## 2. 架构:组件与职责 + +### 2.1 `MetaProgress`(新 Autoload)—— 全局元进展状态 +`scripts/autoloads/meta_progress.gd`,注册进 `project.godot`。 +``` +const PATH := "user://meta.json" +var aether: int = 0 +var unlocked: Array = [] # 已解锁法术 id(String) + +func _ready() -> void: load_meta() +func add_aether(n: int) -> void # 累加并存档(n<=0 直接返回) +func is_unlocked(id: String) -> bool # id 在 unlocked 中 +func unlock(id: String, cost: int) -> bool # 若已解锁 或 aether void # 读 meta.json;缺失/损坏→aether=0, unlocked=[] +func save_meta() -> void # 写 { "aether": int, "unlocked": [...] } +func clear() -> void # aether=0, unlocked=[], 删 meta.json(ST-03 调用) +``` +- 职责边界:只管"有多少碎片、解锁了哪些、怎么存读"。不知道法术费用(费用来自 spells.json,调用方传入)、不碰商店/UI。 + +### 2.2 `spells.json` 门控字段 +给 6 张进阶法术的 `meta` 加 `"unlock_cost": N`(放 **meta 内**,法术编辑器把 meta 当自由 JSON 保留,不会像顶层字段那样被 rebuild 丢弃)。无 `unlock_cost` 或 =0 → 开局解锁。 + +### 2.3 商店池门控(`shop_manager.gd`) +`_refresh_slots` 现有过滤 `shop_cost>0`,改为: +``` +shop_cost>0 且 (unlock_cost==0 或 MetaProgress.is_unlocked(id)) +``` +锁定且未解锁的法术不进商店池。 + +### 2.4 碎片结算(`combat_manager.gd`) +`_on_player_died` 已在重置前捕获到达波次。新增:`MetaProgress.add_aether(_aether_for_wave(wave))`,其中 +``` +func _aether_for_wave(wave) -> int: return int(pow(float(wave), 1.4) / 3.0) +``` +(W1=0 / W5≈3 / W10≈8 / W20≈22,贴合 §7.1 的 0/3/8/20)。 + +### 2.5 解锁界面(`main_menu.gd`) +主菜单新增「🔓 解锁」按钮 → 程序化 CanvasLayer 面板(比照现有 settings 面板写法): +- 顶部显示 `以太碎片: N`。 +- 遍历 `SpellRegistry` 中 `meta.unlock_cost>0` 的法术,逐行:`显示名 — 费用 X 碎片 — [解锁]按钮`。 +- 按钮态:已解锁→显示"已解锁"禁用;未解锁且碎片不足→禁用;足够→可点,点击 `MetaProgress.unlock(id, cost)` 成功后刷新面板。 +- 关闭按钮返回菜单。文案走 i18n(新增 `META_*` 键,4 语言)。 + +--- + +## 3. 数据流 +``` +开始→游玩→死亡 + │ combat_manager._on_player_died 捕获 wave + ▼ MetaProgress.add_aether( _aether_for_wave(wave) ) → 存 meta.json(全局持久) +主菜单「🔓解锁」面板 → 花碎片 MetaProgress.unlock(id,cost) → unlocked[]+存档 + ▼ +下一局商店 _refresh_slots:池 = shop_cost>0 且 (unlock_cost==0 或 已解锁) +``` + +--- + +## 4. 数值 + +**默认锁定的 6 张法术 + 费用**(写入各自 `meta.unlock_cost`): +| 法术 id | unlock_cost | 说明 | +| :--- | :--- | :--- | +| action_energy_orb | 4 | 慢速高伤 | +| action_frost_bolt | 5 | 冻结 | +| action_poison_pool | 6 | 地面毒池(zone) | +| logic_loop | 6 | 循环(强力) | +| action_chain_bolt | 8 | 连锁闪电 | +| action_summon_turret | 10 | 召唤炮台 | + +其余 ~13 张(spark_bolt / fire_bolt / poison_dart / laser_beam / water_wave / damage_plus / spread_mod / double_cast / pierce_plus / trigger_on_hit / every_n_shots / if_hp_below / if_enemy_nearby)开局可用。解锁全部 = 39 碎片 ≈ 5 局(W10 档)。 + +**碎片公式**:`int(pow(wave, 1.4) / 3.0)`。 + +> `action_plasma_storm` 是共鸣产物、`shop_cost=0`,本就不在商店池,无需门控。 + +--- + +## 5. 边界与默认 +- `meta.json` 缺失/损坏 → 全新档(aether 0、unlocked 空),不崩溃。 +- 已解锁法术再点解锁 → `is_unlocked` 提前拦截 / 按钮禁用,不重复扣费。 +- 碎片不入局内存档(ProfileManager A/B)——它是全局的,只在 meta.json。 +- ST-03 清除数据:`SettingsManager.clear_all_local_data` 增调 `MetaProgress.clear()`。 +- 门控只影响**商店掉落池**;玩家的初始预设卡组(action_spark_bolt)不受影响。 + +--- + +## 6. 测试计划(Godot MCP 运行时实测) +1. **持久往返**:`MetaProgress.add_aether(10)` → `save_meta` → 新实例 `load_meta` → aether=10、unlocked 保留。 +2. **解锁扣费**:aether=5,`unlock("action_frost_bolt", 5)`→true、aether=0、is_unlocked=true;再 `unlock("action_chain_bolt", 8)`→false(不足)、状态不变。 +3. **商店池门控**:unlocked 为空时 `_refresh_slots` 抽样多次,锁定法术(如 chain_bolt)**不出现**;解锁后再抽,可出现。 +4. **碎片公式**:`_aether_for_wave` 对 1/5/10/20 返回 0/3/8/22。 +5. **清除数据**:`MetaProgress.clear()` 后 aether=0/unlocked 空/文件删除。 +6. **解锁面板**:主菜单点「解锁」渲染碎片数 + 6 行锁定法术;截图确认;点解锁按钮态正确变化。 + +--- + +## 7. 涉及文件清单 +| 文件 | 改动 | +| :--- | :--- | +| `scripts/autoloads/meta_progress.gd` | **新建** MetaProgress autoload | +| `project.godot` | 注册 MetaProgress autoload | +| `data/spells.json` | 6 张法术 meta 加 `unlock_cost` | +| `scripts/autoloads/shop_manager.gd` | `_refresh_slots` 过滤加门控条件 | +| `scripts/domain/combat/combat_manager.gd` | `_on_player_died` 按波次发碎片 + `_aether_for_wave` | +| `scripts/autoloads/settings_manager.gd` | `clear_all_local_data` 增调 `MetaProgress.clear()` | +| `scenes/ui/main_menu.gd` | 「🔓解锁」按钮 + 解锁面板 | +| `translations/*.po` | `META_*` 四语键 | + +--- + +## 8. 完成定义 (DoD) +- 上述 6 项测试在 Godot 4.6 运行时全绿、0 编译错误。 +- 死亡后碎片按波次增加并持久(重启游戏仍在)。 +- 主菜单解锁面板可花碎片解锁法术;解锁后该法术出现在商店池、未解锁前不出现。 +- ST-03 清除数据把碎片/解锁清空。 +- `game_design §7` 现状 callout 更新为「碎片+解锁树 已实现(MVP);图鉴/成就/核心解锁待后续」。