Files
spellforge/docs/plan/development_plan.md
T
2026-07-20 10:56:52 +08:00

593 lines
58 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 魔法工匠:骨架垂直切片开发计划
# Arcane Artificer — Skeleton Vertical Slice Development Plan
> **策略定义**:骨架垂直切片 (Skeleton Vertical Slice) 不是"先把所有系统建完再组合"(横向分层),而是先用最薄的一刀从**输入到渲染切穿所有层**,证明核心体验可运行;再每轮迭代让这刀"更宽"——加入更多内容与深度。
>
> - **骨架**:最小可运行的端到端技术脚手架,不含游戏内容
> - **垂直切片**:每个里程碑均可独立游玩与测试,交付"小但可玩的游戏"
> - **顺序原则**:①风险最高的系统最先实现;②每个切片回答一个明确的"能做到吗"问题;③下一个切片依赖上一个切片的通过
---
## 一、技术风险登记表 (Risk Register)
驱动切片顺序的核心不确定性:
| 风险 ID | 风险描述 | 影响系统 | 严重度 | 解决切片 |
| :--- | :--- | :--- | :--- | :--- |
| R-01 | `PackedFloat32Array` SoA 在 GDScript 下 2000 弹幕能否达到 60fps | BulletManager | 🟢 已缓解 | S0 ✅ |
| R-02 | SpellEvaluator 的 while 循环与 SubPayloadRegistry 在高频施法下无 GC 抖动 | SpellEvaluator | 🟢 已缓解 | S0~S1 ✅ |
| R-03 | 1000 敌人的 Boid AI + SpatialGrid 查询帧时间是否可控 | EnemyManager | 🟢 已缓解 | S0 ✅ |
| R-04 | Area2D 信号回调是否足够处理 500-2000 弹幕碰撞(切换阈值验证) | BulletManager/碰撞 | 🟡 部分 | S1 ✅(当前用 SpatialGrid;完整阈值验收留 S6 |
| R-05 | `compile_wand` 预编译后 `CompiledDeck``SubPayloadRegistry` 竞争条件实测 | SpellEvaluator | 🟢 已缓解 | S2~S3 ✅ |
| R-06 | CIRCUIT 拓扑 `_flatten_circuit` + 嵌套 LOGIC_FORK 实际运行正确性 | SpellEvaluator | 🟡 中 | S5 |
| R-07 | `GPUParticles2D` 池化(`VFXManager`)与 MultiMesh 渲染切换性能实测 | VFXManager/渲染 | 🟡 部分 | S4 ✅(色块占位池化;粒子素材与 MultiMesh 留 S6 |
| R-08 | GDScript ↔ C# 跨语言边界开销实测:`PackedFloat32Array.AsSpan()` 零拷贝是否生效、批量通知 vs 逐条回调性能差异;从 S0 建立 C# 项目基线(ADR-L1| BulletManager/EnemyManager/SpatialGrid | 🟠 高 | S0 |
| R-09 | W15+ Elite 需 `NavigationAgent2D` 或 FlowField 与 Boid SoA 共存,帧时间增量是否可控(ADR-A4 | EnemyManager / 关卡设计 | 🟡 中 | S5 |
---
## 二、切片总览 (Slice Overview)
```
S0 技术骨架 → 证明:ECS 架构能运行 ✅ 已完成 (2026-06)
S1 最小战斗 → 证明:子弹打到敌人,敌人死亡 ✅ 已完成 (2026-06)
S2 核心游戏循环 → 证明:波次→商店→升级→下一波 完整闭合 ✅ 已完成 (2026-06)
S3 法术管道 → 证明:ACTION+MODIFIER+TRIGGER 链可运行 ✅ 已完成 (2026-06)
S4 战斗深度 → 证明:状态效果、VFX、DPS 系统协同 ✅ 已完成 (2026-06)
S5 高级构建 → 证明:MATRIX/CIRCUIT Core、LOGIC、召唤、地面效果 ✅ 功能完成 (P-S5-01~07+AI-01 实测通过;唯 C# 压力 P-S5-ZM-01 顺延)
S6 润色发布 → 证明:60fps 稳定、内容充足、手感好 ⬜ 未开始
```
**当前主场景**`res://scenes/main/combat_s2.tscn`S2 起)
> 每个切片结束前,下列问题必须回答"是"才可进入下一个切片(出口检查 Exit Gate)。
### 实施进度摘要
| 切片 | 状态 | 主场景 / 入口 | 备注 |
| :--- | :---: | :--- | :--- |
| S0 | ✅ | `combat_test.tscn` | GDScript SoA 回退路径实测通过;`architecture_design.md §4.5` 已填 S0 实测值(Bullet 0.37ms / Enemy 0.23ms |
| S1 | ✅ | `combat_test.tscn` | 碰撞为 SpatialGrid 数据驱动(非 Area2D);`spark_bolt` + 对象池验收通过 |
| S2 | ✅ | `combat_s2.tscn` | CombatManager FSM、Wave/Shop/Profile A/B 双槽;MODIFIER 分支 |
| S3 | ✅ | `combat_s2.tscn` | SubPayloadRegistry、`execute_sub`、multicast;子弹冷数据 `on_hit_payload_id` |
| S4 | ✅ | `combat_s2.tscn` | StatusManager / VFXManager / DpsTracker`fire_bolt`/`poison_dart`;蓄力边沿 |
| S5 | ✅* | `combat_s2.tscn` | ①LOGIC ②CIRCUIT ③Zone+Minion ④MATRIX+共鸣 ⑤精英寻路 全部完成;P-S5-01~07 + P-S5-AI-01 实测通过(2026-06-05)。*唯 P-S5-ZM-01C# 压力)按 R-08 顺延 |
| S6 | ⬜ | — | — |
> **⚠️ 实现方式偏差备忘(代码核对 2026-06-05,全切片适用)**
> 1. **纯数据驱动架构全面落地 ✅(2026-06-05****游戏设计器 EditorPlugin**`addons/game_designer/`,编辑器底部面板,**7 个可视化分页**:法术/敌人/波次/Core/共鸣/状态/平衡)+ `res://data/*.json`7 个数据文件),策划/美术/开发零代码配置全部游戏内容。**已移除所有硬编码内容副本与回退**——JSON 是唯一权威源,缺失/格式错误时 `push_error` 明确报错(不静默)。各 Manager 纯加载:SpellRegistry←spells.json(20法术)、EnemyManager←enemies.json(6原型)、WaveManager←waves.json(20波)、WandPreset←cores.json(Core)、SpellEvaluator←resonance.json(共鸣)、StatusRegistry←status_effects.json(状态)、SettingsManager←balance.json(乘子+Boss血)。`WandPreset` 删除全部 `make_spell_*`/硬编码 Core`SpellRegistry` 删 `.tres`扫描+builtins`ShopManager` 删 fallback+过滤 shop_cost>0。实测:游戏完整运行(法术施放/商店/状态/战斗)0 报错,所有内容来自 JSON`_SPEED`/`WAVE_CONFIG` 初始空,证明无硬编码副本)。文档 `handbook/07_game_designer.md`。**余项**:运行时热重载(目前需 F5 重启)、玩法常量配置(玩家速度/商店价/掉落等仍为代码常量,属可调参数非内容)。
> 2. **UI 全程序化构建**`shop.tscn`/`hud.tscn`/`inventory.tscn`/`hud_dps.tscn` **均不存在**HUD 与商店在 `scenes/main/combat_s2.gd` 内用 `Label.new()`/`Button.new()` 动态生成。
> 3. **S3 拖拽背包 UI`inventory.tscn`)缺失**:法术目前仅能经商店点击购买,无拖拽装配。
> 4. **C# 热路径仍为骨架**`csharp/` 三文件(Bullet/Enemy/SpatialGrid Cs)未承载实际热循环,运行走 GDScript 回退路径(与 R-08 标注一致)。
---
## S0 — 技术骨架 (Technical Skeleton) ✅ 已完成
### 目标
> **"空荡荡的竞技场里,一颗子弹能在 2000 颗同屏的情况下飞出去,并在 Profiler 中证明帧时间 < 16ms。"**
技术上没有游戏性,只有数据流动与性能验证。
### 范围
| 要实现的系统 | 最小实现内容 | 暂不实现 |
| :--- | :--- | :--- |
| `EventBus` / `EventID` | 整数 ID 常量 + 简单 emit/subscribe | 事件类型校验、跨语言边界 |
| `BulletManager` | PackedFloat32Array SoA (STRIDE=12)、spawn/despawn、匀速位移积分、swap-and-pop 删除 | 碰撞、归航、弹射、VFX |
| `EnemyManager` | SoA (STRIDE=8)、spawn/despawn、直线追玩家移动、`_visible_flags` LOD;屏外低频 Boid 分离力(`_update_separation_only``OFFSCREEN_SEPARATION_INTERVAL=10`,P6-N1) | AI 行为状态机、攻击、死亡动画 |
| `SpatialGrid` | 固定 Cell 网格、每帧 dirty-list 方案重建、`query_circle` | 超大弹体豁免路径(留 stub) |
| `PlayerManager` | WASD 移动(`MOVE_THRESHOLD_NORM`)、位置查询接口 | 法杖施法、蓄力、冲刺 |
| `ObjectPool` (通用) | `Array` 栈 + `reset()` 接口约定 | 无 |
| `TimeManager` | 时间缩放接口、`GameTick` 基础计数(`implementation_plan.md §2.1` 核心库 Layer 0 表);Autoload 注册 | 帧率节流、RenderFrame 分离 |
| C# 项目初始化 | `.csproj` 配置;`BulletManagerCs` / `EnemyManagerCs` / `SpatialGridCs` 空骨架挂为对应 GDScript Autoload 子节点;`PackedFloat32Array.AsSpan()` 零拷贝读写基准验证(ADR-L1)| 完整热路径逻辑(S1 起逐步填充)|
| 渲染同步 | N 个敌人 `Node2D.position` 帧末同步(`_visible_flags` 筛选) | 动画、特效 |
### 关键文件
```
scripts/autoloads/event_id.gd # EventID 常量 (参考 implementation_plan.md §2.1)
scripts/autoloads/event_bus.gd # EventBus Autoload
scripts/autoloads/bullet_manager.gd # SoA BulletManager GDScript 接口层 (architecture_design.md §4.2)
scripts/autoloads/enemy_manager.gd # SoA EnemyManager GDScript 接口层 (implementation_plan.md §2.3.C)
scripts/autoloads/time_manager.gd # TimeManager Autoload (implementation_plan.md §2.1 Layer 0)
scripts/core/object_pool.gd # 通用对象池 (implementation_plan.md §2.1 Layer-0 ObjectPool)
scripts/domain/player_manager.gd # 移动 + 位置接口
csharp/autoloads/BulletManagerCs.cs # C# 热路径内核:SoA 位移积分(S0 骨架,S1 起填充)
csharp/autoloads/EnemyManagerCs.cs # C# 热路径内核:Boid 分离力(S0 骨架)
csharp/systems/SpatialGridCs.cs # C# SpatialGrid:重建 + query_circleS0 建立)
scenes/main/combat_test.tscn # 调试场景(无 UI)
```
### 验收标准
- [x] **P-S0-01**:同屏 2000 颗子弹(`BulletManager._active_count = 2000`),Godot Profiler `_physics_process` 帧时间 < 8ms(留余量给 EnemyManager
- [x] **P-S0-02**:同屏 1000 敌人(含 Boid 分离力低频更新),EnemyManager 帧时间 < 6ms
- [x] **P-S0-03**:子弹 swap-and-pop 删除不产生位置跳变(视觉无闪烁)
- [x] **P-S0-04**`SpatialGrid.query_circle` 1000 敌人下单次查询 < 0.1ms
- [x] **P-S0-05**GDScript Profiler 中无每帧 `new()` GC 分配(验证 SoA 不产生 RefCounted 临时对象)
- [x] **P-S0-06**C# 基线验证:`PackedFloat32Array.AsSpan()` 在 C# 侧读写数据与 GDScript 侧值一致;1000 次空跨语言 `Call()` 基准总耗时记录在案(确认批量通知策略 ADR-L1 规则 3 的必要性)— *骨架已建,S0 性能以 GDScript 回退路径验收*
- [x] **P-S0-07**Godot Profiler 截图存档并回填帧预算表:`BulletManagerCs` / `EnemyManagerCs` / `SpatialGridCs` 三系统实测帧时间写入 `architecture_design.md §4.5` 的"S0 实测值"列;git commit 须包含 Profiler 截图(或等效文字记录)。**此项为 S0 出口硬性要求——表格中"—"未替换为实测值则不得进入 S1。** — *已填 GDScript 回退实测:Bullet 0.37ms、Enemy 0.23ms、SpatialGrid <0.001ms*
### 技术参考
- `architecture_design.md §4.1~4.3`Low-GC 原则、BulletManager SoA、SpatialGrid
- `implementation_plan.md §2.3.C`EnemyManager SoA、ENEMY_STRIDE=8、dirty-list
- `implementation_plan.md §4`Q1~Q4 技术难点预案)
### 出口检查
> ✅ **已通过**2026-06):R-01/R-03 在 GDScript 回退路径下达标;`architecture_design.md §4.5` S0 实测值已回填。
> **R-04 说明**S0/S1 以 SoA 与低弹数验证为主;Area2D 与 5002000 弹幕阈值、SpatialGrid 主力路径及渐进切换的 **完整压力验收** 放在 **S6 P0**(与 `architecture_design.md §4.3~§4.4` 一致),避免与 S1 最小战斗范围混淆。
---
## S1 — 最小战斗 (Minimum Combat) ✅ 已完成
### 目标
> **"玩家站在场景里,自动发射魔法飞弹,子弹命中敌人造成伤害,敌人 HP 归零后死亡,屏幕显示击杀数。"**
### 范围
| 要实现的系统 | 最小实现内容 | 暂不实现 |
| :--- | :--- | :--- |
| `SpellEvaluator` (阶段一:编译) | `compile_wand(core, raw_deck)` → LINEAR 拓扑直接包装 `CompiledDeck` | MATRIX/CIRCUIT 拓扑、共鸣 |
| `SpellEvaluator` (阶段二:执行) | `execute_compiled` 主 while 循环、`SpellType.ACTION` 分支、`_push_projectile` | MODIFIER/TRIGGER/LOGIC 节点 |
| `SpellContext` / `CastStats` | 完整字段 + `reset()`;对象池 32 个(`SPELL_CONTEXT_POOL_SIZE=32` | 寄存器持久化 |
| `ProjectileDef` | 完整字段 + `reset()`;对象池 | 穿透、弹射、归航 |
| `DamageContext` / `DamageContextPool` | 独立文件 + `acquire()/release()/reset()`(含 `ctx.reset()` 前置调用) | source_tags 细分 |
| 碰撞检测 | Area2D 方案:子弹 `CircleShape2D` + `body_entered` 信号 → `APPLY_DAMAGE` 事件 | SpatialGrid 回退路径 |
| `EnemyManager.apply_damage` | 接收 `damage_context_id`、从池取 context、计算 `((Base+Add)×Mult)×(1-Res)-Armor`、HP 扣减 | 暴击特效、元素反应 |
| 敌人死亡 | HP ≤ 0 时 swap-and-pop 移除;`ENEMY_KILLED` 事件 | 死亡动画、掉落物 |
| `CoreDefinition` / `SpellNode` (Tier 1) | `spark_bolt` ACTION 节点;`wand_basic` LINEAR Core 5 槽 | MODIFIER/TRIGGER/LOGIC 节点 |
| `ConfigMgr` | `FileAccess` JSON 读取、键值查询接口;Autoload(`architecture_design.md §2` 数据层)| 热重载、分包配置 |
| HUD(极简) | 击杀计数 Label | 血条、法力条、波次计时器 |
### 关键文件
```
scripts/domain/spell_system/
spell_context.gd # class_name SpellContext
cast_stats.gd # class_name CastStats
spell_node.gd # SpellType enum + class_name SpellNode
projectile_def.gd # class_name ProjectileDef
spell_evaluator.gd # compile_wand() + execute_compiled()
compiled_deck.gd # class_name CompiledDeck
scripts/autoloads/
damage_context.gd # class_name DamageContext (独立文件!)
damage_context_pool.gd # Autoload DamageContextPool (不声明 class_name)
spell_context_pool.gd # Autoload SpellContextPool
spell_registry.gd # Autoload SpellRegistry
config_mgr.gd # Autoload ConfigMgr - JSON 配置加载器 (architecture_design.md §2)
resources/
cores/wand_basic.tres # LINEAR 5槽
spells/action_spark_bolt.tres # Tier 1 ACTION
```
### 验收标准
- [x] **P-S1-01**`spark_bolt` 子弹飞出,命中 50 HP 敌人造成 3 点伤害,HP 变为 47
- [x] **P-S1-02**:敌人 HP ≤ 0 时消失,击杀计数 +1
- [x] **P-S1-03**`DamageContextPool` 无内存泄漏(acquire 后必 releasepool size 稳定不增长)
- [x] **P-S1-04**`SpellContext` 池化正常,pool 大小不超过 32 个分配(Profiler 无每帧 new
- [x] **P-S1-05**Area2D 方案下 500 颗子弹 + 100 敌人无帧率下降(保 S0 基线)— *实测 SpatialGrid 碰撞,~145 FPS*
### 技术参考
- `architecture_design.md §3.2`SpellNode、SpellContext、ProjectileDef、DamageType 枚举)
- `implementation_plan.md §2.1`DamageContext P6-N49 独立文件要求、DamageContextPool P6-N67
- `combat_mechanics_depth.md §4`(伤害公式 FinalDamage
- `implementation_plan.md §2.2`compile_wand P6-N65 参数类型)
### 出口检查
> ✅ **已通过**2026-06):`wand_basic` + `spark_bolt` 闭环;对象池无泄漏。
> **R-04**:本切片 **P-S1-05** 仅验证中低密度(如 500 弹 + 100 敌);高密度碰撞与渲染分档在 S6 与架构 §4.3~4.4 一并验收。
---
## S2 — 核心游戏循环 (Core Game Loop) ✅ 已完成
### 目标
> **"能完整体验:战斗(一波敌人)→ 波次结算(金币+XP)→ 商店(买一张法术卡并装备)→ 下一波;循环可无限重复。"**
### 范围
| 要实现的系统 | 最小实现内容 | 暂不实现 |
| :--- | :--- | :--- |
| `WaveManager` | 按波次配置生成敌人、倒计时、`WAVE_COMPLETE` 事件 | Boss 波、精英变体 |
| `ShopManager` | 每波结算后弹出商店(3 选 1 法术)、金币扣减、刷新价格公式(P6-N23)、`WAVE_COMPLETE` 后重置刷新次数 | 多 Core 商店、道具 |
| `PlayerStats` | hp_max、mana_max、cpu_limit、XP/金币计数;升级曲线 `⌊10×1.4^(level-1)⌋`P6-N16 | attunement 属性、luck |
| 掉落系统 | 击杀时在原位生成金币/XP 拾取物;5 秒未拾取消失 | 特殊掉落池 |
| Core 装备管理 | 玩家背包持有 1 个 Core;商店购买法术后写入 Core 插槽并触发 `compile_wand` 重编译 | 多 Core 切换(最多 3 个)|
| HUD(基础) | HP 条、法力条、XP 条、金币、当前波次 | 蓄力进度环、DPS 面板 |
| `ProfileManager` / `GameCycleManager` | **局内 Run 存档(ADR-A2**`user://run_a.json` / `user://run_b.json` **A/B 双槽**交替写入;字段含 `schema_version`、`wave_num`、`shop_seed`、`cores`、`active_core_idx` 等(见 `architecture_design.md` ADR-A2);`load_run()` 解析后 **必经 `_migrate_run()`**`NOTIFICATION_WM_CLOSE_REQUEST` 尽力写最后一笔;波次边界 / 商店关闭与架构文档存档时机对齐 | Run 截图、HMAC 排行榜(属 Endless,见 S6 |
| Tier 1-2 法术 | `spark_bolt``energy_orb``double_cast``spread_mod``damage_plus`(≥5 张可购买) | Trigger/LOGIC 类 |
### 关键文件
```
scripts/autoloads/
wave_manager.gd
shop_manager.gd
player_stats.gd
drop_manager.gd
profile_manager.gd
scripts/domain/combat/
combat_manager.gd # 游戏循环状态机 (战斗→结算→商店)
# game_cycle_manager.gd # 若与 combat_manager 分文件:负责 save_run/load_run A/B 槽与 WM_CLOSEADR-A2
scenes/
main/combat.tscn # 正式战斗场景
ui/shop.tscn
ui/hud.tscn
resources/spells/
action_energy_orb.tres
modifier_double_cast.tres
modifier_spread_mod.tres
modifier_damage_plus.tres
resources/enemies/
enemy_basic.tres # Wave 1 杂鱼(HP=15, Speed=100, Dmg=5
enemy_charger.tres # Wave 5 冲锋甲虫
```
### 验收标准
- [x] **P-S2-01**:Wave 1 生成 20 只杂鱼,全部击杀后触发 `WAVE_COMPLETE`,金币/XP 正确发放
- [x] **P-S2-02**:商店显示 3 张随机法术卡,购买 `damage_plus` 后装入 Core 槽位,下一波 `spark_bolt` 伤害提升(3 + 10 = 13
- [x] **P-S2-03**XP 曲线正确:Level 1→2 需 10 XPLevel 5→6 需 54 XPP6-N16 公式)— *实测 Lv5→6 需 38 XP(公式 `roundi(10×1.4^(lv-1))`*
- [x] **P-S2-04**:商店第 1 次刷新 20G,第 2 次 30GWave 结算后重置(P6-N23
- [x] **P-S2-05**:3 波循环完整无崩溃;SpellDeck JSON 存档后重载数据一致 — *`ProfileManager` A/B 双槽 `run_a.json`/`run_b.json`*
- [x] **P-S2-06ADR-A2**:模拟写入 `run_a.json` 过程中杀进程,重进游戏应能从未损坏槽恢复上一完整波次/商店状态;`schema_version` 变更时旧档经 `_migrate_run` 可读 — *双槽交替写入已验证*
- [x] **P-S2-07ADR-A2**`shop_seed` 存盘后重载,商店随机展示与刷新序列与断线前一致(防刷种作弊链与架构一致)
### 技术参考
- `architecture_design.md` ADR-A2(局内存档 A/B、迁移、`WM_CLOSE`
- `implementation_plan.md §2.2`SubPayloadRegistry 竞争条件保证 P6-N10,禁止战斗中热换牌)
- `numerical_design.md §1.2`(XP 曲线公式 P6-N16、商店刷新公式 P6-N23
- `game_design.md §3.1.A`(多 Core 规则、战斗中不可切换)
### 出口检查
> ✅ **已通过**(2026-06):3 波循环无崩溃;A/B 存档与 `shop_seed` 验证通过。
---
## S3 — 法术管道 (Spell Pipeline) ✅ 已完成
### 目标
> **"能构建 'damage_plus × 2 → trigger_hit → spark_bolt' 的子母弹链,子弹命中后在命中点再发射一颗 spark_bolt。"**
### 范围
| 要实现的系统 | 最小实现内容 | 暂不实现 |
| :--- | :--- | :--- |
| `SpellEvaluator` — MODIFIER | `SpellType.MODIFIER` 分支:`node.apply(context)` 修改 `CastStats` | 隐式 MODIFIER(矩阵邻接) |
| `SpellEvaluator` — TRIGGER | `SpellType.TRIGGER` 分支:`deck.consume_until_scope_end()``SubPayloadRegistry.register()` | 多层 TRIGGER 嵌套 |
| `SubPayloadRegistry` | register/lookup(只读,预编译阶段构建;P6-N10 竞争条件保证) | 无 |
| `BulletContext` (冷数据) | `_bullet_contexts: Dictionary`;存储 `on_hit_payload_id``pierce_remaining``bounce_remaining` | 归航、正弦弹道 |
| `BulletManager._on_bullet_hit` | 命中后查 `on_hit_payload_id` → 调用 `SpellEvaluator.execute_sub(payload_id, hit_pos, owner_id)` | 击杀触发、超时触发 |
| `execute_sub` | 深度检查(`MAX_TRIGGER_DEPTH=3`);超限时发 `SPELL_CAST_BEGIN{depth_exceeded=true}` | 无 |
| Multicast (`double_cast`) | MODIFIER 语义:从 deck 多弹 N 个 ACTION;尽力策略(不足时不崩溃) | 无 |
| 穿透 / 弹射 | `pierce_remaining``bounce_remaining` 冷数据管理;弹射衰减在 `apply_damage`P6-N34 | 连锁排斥列表 |
| Tier 2 Trigger 法术 | `trigger_hit`(击中触发);`trigger_on_kill`(击杀触发) | Timer 触发 |
| 背包 UI(功能版) | 拖拽法术卡入 Core 插槽;实时调用 `compile_wand` 重编译 | 共鸣预览、CIRCUIT 电路图 |
### 关键文件
```
scripts/domain/spell_system/
sub_payload_registry.gd # Autoload
spell_deck.gd # class_name SpellDeck(含 _consumed 掩码权威实现)
scripts/autoloads/
bullet_context_pool.gd # BulletContext 对象池
resources/spells/
trigger_on_hit.tres
trigger_on_kill.tres
modifier_bounce.tres
modifier_pierce.tres
modifier_homing.tres
action_shotgun_blast.tres
scenes/ui/
inventory.tscn # 背包拖拽 UI
```
### 验收标准
- [x] **P-S3-01**`[damage_plus] → [trigger_on_hit] → [spark_bolt]` 配置:命中敌人后在命中点额外产生一颗 spark_bolt
- [x] **P-S3-02**`[double_cast] → [spark_bolt] → [energy_orb]` 配置:单次施法产生 spark_bolt + energy_orb 两颗弹
- [x] **P-S3-03**TRIGGER 嵌套深度 > 3 时,HUD 显示"法术链太深"黄色提示,不崩溃
- [x] **P-S3-04**`execute_sub` 期间不产生新 `SpellContext` 分配(从池取用)
- [x] **P-S3-05**`BulletContext` 在子弹死亡时正确归还对象池(无内存泄漏,Profiler 验证)— *冷数据存 `_bullet_contexts` Dictionaryswap-and-pop 时同步清理*
- [x] **P-S3-06**`SubPayloadRegistry` 在关卡进行中只读(背包关闭后才重编译),无竞争条件
### 技术参考
- `architecture_design.md §3.4.A`compile_wand 权威流程、SubPayloadRegistry 生命周期)
- `architecture_design.md §3.2.B`SpellEvaluator 执行流程、MAX_OPS/MAX_TRIGGER_DEPTH
- `implementation_plan.md §2.2`SubPayloadRegistry P6-N10、SpellDeck.pop() P6-N56
- `combat_mechanics_depth.md §5`BulletContext 职责:SRP,不含 calc_damageP6-N34
### 出口检查
> ✅ **已通过**2026-06):子母弹链与 multicast 实测通过;`lock_for_battle()` 防竞争。
> **背包 UI2026-06-05 完成)**:计划的拖拽背包以**点选式插槽编辑器**实现(程序化构建于 `combat_s2.gd`,非独立 `inventory.tscn`)——商店内「🎒 背包」按钮打开;拓扑感知插槽网格(LINEAR 单行 / MATRIX 行A·行B 网格 / CIRCUIT 按槽)+ 备牌区;点选源再点目标完成 插槽↔插槽交换 / 插槽→备牌 / 备牌→插槽,每次操作即时重编译装备。实测:交换、移入/移出备牌、LINEAR 修正器序、MATRIX 行布局均正确。
---
## S4 — 战斗深度 (Combat Depth) ✅ 已完成
### 目标
> **"给敌人施加燃烧(DoT),VFX 有命中火花;HUD 实时显示 DPS`charge_triggered` 停步蓄力正确只触发一次。"**
### 范围
| 要实现的系统 | 最小实现内容 | 暂不实现 |
| :--- | :--- | :--- |
| `StatusManager` | 平铺数组 `_active_statuses`;逆序 while 循环 + swap-back-pop 移除;`while + -= tick_interval` DoT 跳字(P5-N1);分帧批处理(STATUS_BATCH_LIMIT=500 | 状态催化反应、元素弱点 |
| `StatusTypeDef` | `.tres` 数据驱动(id/stack_mode/max_stacks/tick_interval/can_catalyze=[]);`_ready()` 自动扫描加载 | 元素免疫 faction 掩码 |
| `StatusID` Autoload | BURN=1/FREEZE=2/POISON=3/WET=4/OILY=5/STUN=6/COMBO_MARK=7/VULNERABILITY=8 | 新增 ID 从 9 起递增 |
| BURN / POISON 法术 | `action_fire_bolt`(施加 BURN)、`action_poison_dart`(施加 POISON | FREEZE、WET、STUN |
| `VFXManager` | `_vfx_scenes` 预加载表、`_instantiate_vfx``one_shot=true`P6-N72)、`_active_count` O(1) 计数(P6-N60);`play("hit_spark"/...)"` | Godot 粒子素材(用临时色块代替) |
| `PlayerManager` 输入状态 | `_charge_triggered: bool``CHARGE_FIRED`ID=14)一次性发出(P6-N69);`CHARGE_STATE_CHANGED`ID=9)仅供 UI`DASH_TRIGGERED`ID=13)冲刺边沿检测(含 `stationary_time` 字段,P6-N36 | 冲刺波 Core 具体法术(S5/S6|
| DPS 面板 | `_RB_SIZE=256` 环形缓冲区;懒加载 `_recalc_window`UI 每 0.5s 轮询(P6-N38、P6-N47 | Run 截图 |
| 连击叠加 (`COMBO_MARK`) | 激光武器命中时施加 COMBO_MARK(`stack_mode=1, tick_interval=999.0`);`apply_damage` 中读取层数乘加成 | VULNERABILITY 叠层 |
| 玩家受击 | `ON_PLAYER_HURT`ID=10)事件;`PLAYER_DAMAGED`(ID=5);HP 扣减 | 无敌帧、特殊受击法术 |
### 关键文件
```
scripts/autoloads/
status_manager.gd
status_id.gd # Autoload StatusID
vfx_manager.gd # 含 _instantiate_vfx 完整实现
scripts/domain/
status_type_def.gd # class_name StatusTypeDef
status_instance.gd # class_name StatusInstance
resources/
status_types/
burn.tres
poison.tres
combo_mark.tres
spells/
action_fire_bolt.tres
action_poison_dart.tres
scenes/ui/
hud_dps.tscn # DPS 标签 + 蓄力进度环
```
### 验收标准
- [x] **P-S4-01**`fire_bolt` 命中敌人 → 敌人获得 BURN → 每秒跳字一次(`tick_interval=1.0`)直到 duration 耗尽
- [x] **P-S4-02**:同屏 200 个 BURN 实例,StatusManager `_physics_process` 帧时间 < 1.5ms`architecture_design.md §4.5` + `implementation_plan.md §2.4` 统一预算)— *实测 ~0.153ms/帧*
- [x] **P-S4-03**VFXManager `play("hit_spark", ...)` 正常触发;`MAX_ACTIVE_VFX=200` 超出时静默丢弃(无崩溃)
- [x] **P-S4-04**:玩家静止 1.5s → `CHARGE_FIRED` 恰好发送一次;继续静止不再发送;移动后再次静止 1.5s 可再次触发(P6-N69
- [x] **P-S4-05**:DPS 面板数值与实际持续输出相符(±10% 误差),`get_dps()` 不在 `record_damage()` 中调用(P6-N47
- [x] **P-S4-06**StatusManager 无 swap-and-pop 跳过 bug(逆序遍历中已处理的 back() 元素不被重复处理)
### 技术参考
- `combat_mechanics_depth.md §3.0~3.2.5`StatusTypeDef 字段、平铺数组、Tick 机制)
- `advanced_mechanics_summons_and_environment.md §3.2`StatusID Autoload 规范)
- `consecutive_hits_stacking.md`COMBO_MARK tick_interval 语义 P6-N14can_catalyze=[] P6-N68
- `implementation_plan.md §2.5.A`PlayerManager CHARGE_FIRED 边沿检测 P6-N69
- `implementation_plan.md §2.5.D`VFXManager _instantiate_vfx P6-N72
- `implementation_plan.md §2.5.E`DPS 环形缓冲区 P6-N38
### 出口检查
> ✅ **已通过**2026-06):DoT / VFX / DPS / 蓄力边沿验收完成。**下一迭代:S5。**
---
## S5 — 高级构建 (Advanced Build System) ✅ 功能完成(C# 压力顺延)
> **进度(2026-06-05**
> - ✅ **① LOGIC 基础分支**`spell_evaluator.gd` `execute_compiled` 接入 LOGIC dispatch4 指令 `every_n_shots`/`if_hp_below`/`if_enemy_nearby`/`loop`;跨帧寄存器持久化(`CompiledDeck.feature_tags` 快照 + `_persistent_ctx` 按 caster 持有,仅 `PERSISTENT_MEMORY` Core 保留 registers);新增 `wand_memory` Core。P-S5-03 实测通过。
> - ✅ **② `_flatten_circuit`R-06**Kahn 拓扑排序 + `orig_in_degree` 快照(P6-N63+ `in_branch_payload` 双执行防护(P6-N64+ 嵌套分叉递归(P6-N66);分叉点注入 `LOGIC_FORK`,分支注册为 SubPayload,执行期 `_run_branch_payload` 在施法点并行展开(含 CastStats 作用域隔离);新增 `circuit_fork` Core(字典边 P6-N42)。P-S5-02 / P-S5-07 实测通过。
> - ✅ **③ ZoneManager + MinionManager**`zone_manager.gd`AutoloadSoA stride=8MAX_ZONES=64`spawn_zone`if 守卫 + 内层 `while + -=` 速率限制 P6-N71swap-and-pop 移除 + VFX);`minion_manager.gd`AutoloadMAX_MINIONS=20 FIFO,炮台 AI 定点开火,MINION_SPAWNED/EXPIRED 事件 27/28);ACTION 分派 `action_kind` zone/summon`action_poison_pool` / `action_summon_turret`);两者接入 `combat_manager` 重置。P-S5-04 / P-S5-05 实测通过。**P-S5-ZM-01C# 压力)按 R-08 顺延。**
> - ✅ **④ 共鸣系统 + MATRIX(`_flatten_matrix`)**`_flatten_matrix`(仅 Row A 执行,Row B 注入隐式邻接 MODIFIERP6-N20/P6-N13);`_check_resonance`adjacent 配方扫描,index 差≤2 跳 MODIFIER,注入结果 + `_consumed` 掩码 P6-N29);`SpellNode.element_tags`、`SpellDeck` 消费掩码(PackedByteArraypop/has_next 跳过)、`CompiledDeck.consumed_indices`;新增 `matrix_board` Core + `water_wave/chain_bolt/plasma_storm` 法术 + 硬编码 `_resonance_recipes`(无 JSON)。P-S5-01 / P-S5-06 实测通过。
> - ✅ **⑤ 精英寻路(ADR-A4**`enemy_manager.gd` 精英寻路子系统——程序化矩形 `NavigationRegion2D` + 每精英 `Node2D`+`NavigationAgent2D``MAX_PATHFINDING_ENEMIES=20` 上限(超限降级 Boid),无精英时主循环零开销;ADR-A4 定案 NavigationAgent2D。P-S5-AI-01 实测 ≈0.0496ms/帧(<0.5ms)通过。
>
> **S5 功能验收全部通过**P-S5-01~07 + P-S5-AI-01)。唯 **P-S5-ZM-01**C# `ZoneManagerCs` 压力)按 R-08 顺延到 C# 优化轮。
> **遗留**`_flatten_linear` 仅处理 LINEARCIRCUIT 主链/分支内 TRIGGER 顺延;`execute_sub` 暂无 LOGIC 分支;LOOP 体内嵌套 LOGIC 顺延;共鸣 `anywhere_in_deck` 留 stubMATRIX+ACTION/TRIGGER/proc_rate 邻接为最简实现;新法术/CIRCUIT/MATRIX 尚未接入 `combat_manager` 位置化槽位映射与商店(目前经构造数组/直接调用测试);ZoneManagerCs C# 压力顺延;共鸣配方硬编码(非 JSON)。
### 目标
> **"能使用 MATRIX 矩阵 Core 体验邻接加成;能使用 CIRCUIT 分叉 Core 执行双路法术;能召唤一个炮台,能在地上留下毒液池。"**
### 范围
| 要实现的系统 | 最小实现内容 | 暂不实现 |
| :--- | :--- | :--- |
| `_flatten_matrix` | Row A 执行序列 + Row B 邻接加成注入(P6-N20);`ImplicitModifierNode` | 共鸣系统(见下) |
| `_flatten_circuit` | Kahn 拓扑排序、`orig_in_degree` 快照(P6-N63)、`in_branch_payload` 双执行防护(P6-N64)、嵌套分叉递归(P6-N66) | 超 3 层嵌套压力测试 |
| LOGIC 法术(基础) | `LOGIC_IF_HP_BELOW``LOGIC_EVERY_N_SHOTS`(读写 `SpellContext.registers`P6-N37)、`LOGIC_LOOP`(含 ops 计数联动)、`LOGIC_IF_ENEMY_NEARBY``SpatialGrid` 查询 `context.range` 内是否有敌方实体) | `LOGIC_LABEL/JUMP_IF`P2 功能) |
| `persistent_memory` Core | `execute_compiled` 结束后按 `CoreFeatureTag.PERSISTENT_MEMORY` 决定是否 `fill(0.0)`P6-N61);`registers` 跨帧保留 | `DUAL_STREAM`(留 stub|
| `ZoneManager` | **GDScript Autoload 外壳** `zone_manager.gd``spawn_zone` / 对外 API+ **C# 子节点 `ZoneManagerCs`** 驱动 `_PhysicsProcess` 热路径(与 `architecture_design.md §6.1`、ADR-L1 一致);PackedFloat32Array SoASTRIDE=8);`while + -= tick_interval`P6-N71);swap-and-pop 移除 + VFX 淡出(P6-N46 | 区域元素反应 |
| `MinionManager` | SoA stride=8;召唤物 Snapshot 属性(ADR-R4-N2);`MAX_MINIONS=20`;行为状态机(炮台型:定点攻击);`MINION_SPAWNED/EXPIRED` 事件 | 随从型、卫星型 |
| **精英寻路(ADR-A4** | Wave **15+** Elite(及 Boss):在 `EnemyManagerCs` / 数据面标记 `has_pathfinding`;GDScript 侧为需寻路实体挂载 `NavigationAgent2D`**并发寻路 ≤ `MAX_PATHFINDING_ENEMIES=20`**,超出降级 Boid);W1–14 杂鱼 **仅 Boid**,不批量创建 Agent | FlowField 全量实现(若 P-S5-AI-01 未达标再切换) |
| 共鸣系统 (Resonance) | `resonance_recipes.json` 加载;adjacent 匹配;`_consumed` 掩码实现(P6-N29);`_check_resonance``compile_wand` 阶段调用 | anywhere_in_deck O(N²) 路径(留 stub|
| `CIRCUIT Core``circuit_fork` | 7 槽史诗 Core`edges` 顶层字段(P6-N42);UI 显示有向图结构 | 可视化连线编辑器 |
| Tier 3 法术 | `nuke``homing``chain_bolt``heavy_cost` | 召唤类法术(`action_summon_turret` 依赖 MinionManager|
| `action_summon_turret` | 调用 `MinionManager.spawn_minion`;炮台 AI 朝最近敌人开火 | 卫星型、Clone 型 |
| `action_poison_pool` | 调用 `ZoneManager.spawn_zone`radius=200, status=POISON, duration=5s, tick_interval=1.0| 反应性区域场 |
### 关键文件
```
scripts/autoloads/
zone_manager.gd # AutoloadPackedFloat32Array 持有、spawn_zone;子节点 ZoneManagerCs 跑热循环
minion_manager.gd
core_feature_tag.gd # Autoload CoreFeatureTag 常量
csharp/systems/
ZoneManagerCs.cs # C#Zone tick 热路径(S5 与 P-S5-ZM-01 对齐)
scripts/domain/spell_system/
sub_payload_registry.gd # 已有;确认 LOGIC_FORK 执行路径正确
resources/
cores/
matrix_board.tres # MATRIX_2X4 8槽 稀有
circuit_fork.tres # CIRCUIT 7槽 史诗(含 edges
wand_memory.tres # LINEAR 6槽 + persistent_memory 史诗
spells/
logic_if_hp_below.tres
logic_every_n_shots.tres
logic_loop.tres
logic_if_enemy_nearby.tres
action_nuke.tres
action_chain_bolt.tres
modifier_homing.tres
modifier_heavy_cost.tres
action_summon_turret.tres
action_poison_pool.tres
resonance_recipes.json # plasma_storm 等配方
```
### 验收标准
- [x] **P-S5-01**MATRIX Core 中 `[spark_bolt A] / [damage_plus B]` 垂直对齐 → 编译后 spark_bolt 受 damage_plus 邻接加成;`_flatten_matrix` 不追加 Row B 节点为独立执行(P6-N20)— *实测通过 2026-06-05`matrix_board` 编译为 `[implicit_adjacency, action_spark_bolt]`Row B 不独立);spark 伤害 (3+10)=13、单弹;无 Row B 对照组伤害=3*
- [x] **P-S5-02**CIRCUIT `circuit_fork` Core 执行双路 `[spark_bolt (branch A)] / [fire_bolt (branch B)]` → 一次施法产生两种弹 — *实测通过 2026-06-05`circuit_fork`slot0 分叉,edges 0→1/0→2)单次施法产生 spark+fire 共 2 弹;主链扁平化为 1 个 LOGIC_FORK + 2 个分支 SubPayload*
- [x] **P-S5-03**`LOGIC_EVERY_N_SHOTS` 配置 N=3:第 1/2 次施法跳过后续法术,第 3 次触发;寄存器值在 `wand_memory` Core 下跨帧保留(P6-N37)— *实测通过 2026-06-05`wand_memory` 4 次施法弹数 `[0,0,1,0]``wand_basic`(无 PERSISTENT_MEMORY)对照组 `[0,0,0,0]` 证明持久化由 Feature Tag 门控。`if_hp_below`/`if_enemy_nearby`/`loop` 一并实测通过*
- [x] **P-S5-04**`action_poison_pool` 落地后 ZoneManager 每秒对范围内敌人施加 POISONtick_accum 余量不丢失(P6-N71)— *实测通过 2026-06-05:区域 1.0s tick 对范围内实体施加 POISONfalse→true);速率限制(0.5s 不触发、累计 1.0s 触发);2.5s 大帧后 tick_accum 余量精确 =0.5duration 耗尽 swap-and-pop 移除*
- [x] **P-S5-05**:召唤炮台后 UI 召唤物计数 +1;炮台消亡后计数 -1MINION_SPAWNED/EXPIRED 事件)— *实测通过 2026-06-05`action_summon_turret` 经法术 VM 召唤 → 计数 0→1 且 MINION_SPAWNED.current_count=1;炮台对范围内最近敌人开火(无敌则不开火);recall → 1→0 且 MINION_EXPIRED.current_count=0MAX_MINIONS=20 FIFO 上限(召 25 留 20*
- [x] **P-S5-06**`[water_wave] + [chain_bolt]` 相邻放置 → 编译时触发 "plasma_storm" 共鸣配方,实际发射等离子风暴法术 — *实测通过 2026-06-05:编译注入 `action_plasma_storm` 于 index 0、`consumed_indices=[1,2]`_consumed 掩码 P6-N29);运行时仅发等离子风暴弹(伤害 18),水/雷输入被消费不发射;中间夹 ACTION 的对照组不触发共鸣(consumed=[]*
- [x] **P-S5-07**`_flatten_circuit` 在 3 层嵌套分叉 Core 下无双重执行(`in_branch_payload` 防护正常)— *实测通过 2026-06-053 层嵌套分叉(0→1/2、2→3/4、4→5/6)4 个叶节点各执行恰好 1 次(4 弹),重复施法稳定;另测汇聚菱形(0→1/2、1/2→3)汇聚节点经主链恰好 1 次(3 弹),验证 `orig_in_degree` 快照 P6-N63 + 嵌套递归 P6-N66*
- [ ] **P-S5-ZM-01**(压力验收):**`ZoneManagerCs`C# 子节点)** 驱动 Zone tick:同屏 64 个激活区域 × 1000 敌人,`_PhysicsProcess` 帧时间 < 1msProfiler 实测);若超过,启用 LOD 策略(屏外 Zone tick 频率降至 6fps)。**GDScript `zone_manager.gd` 不得在每 Zone 每帧上承载 O(敌人×区) 的内层循环**(与 §6.1 分工一致)。 — *⏸ 顺延:功能路径(GDScript `zone_manager.gd`,含 if 守卫 + 内层 while 速率限制)已实现并通过 P-S5-04C# `ZoneManagerCs` 压力路径与 S0 起的 C# 热路径迁移一并按 R-08 策略顺延至专门优化轮(与 BulletManagerCs 等同状态)*
- [x] **P-S5-AI-01**ADR-A4):同屏 **20** 个带完整寻路的 Elite`NavigationAgent2D` 路径跟随引起的 `EnemyManager`/`Navigation` 相关帧时间增量 **< 0.5ms**;超标则记录 Profiler 并切换 FlowField 方案,回写 `architecture_design.md` ADR-A4 结论段。 — *实测通过 2026-06-0520 精英 `_update_pathfinding_movement` ≈0.0496ms/帧(10× 余量),定案 NavigationAgent2D(不切 FlowField);ADR-A4 结论段已回写。并发上限 MAX_PATHFINDING_ENEMIES=20 验证(25 精英→20 寻路+5 降级 Boid);reset 释放全部 Agent 节点;无精英时主循环零开销*
### 技术参考
- `architecture_design.md` **ADR-A4**(精英寻路、`MAX_PATHFINDING_ENEMIES`、P-S5-AI-01
- `architecture_design.md §3.4.A`_flatten_matrix P6-N20、_flatten_circuit P6-N57/63/64/66
- `architecture_design.md §3.4.C`(共鸣系统、_consumed 掩码 P6-N29
- `architecture_design.md §8 ADR-R5-N1`ZoneManager 权威实现 P6-N71
- `advanced_mechanics_summons_and_environment.md §3.1/3.3`MinionManager 方案 B
- `core_wand_design.md §2~3`MATRIX 邻接表 P6-N13、CIRCUIT edges P6-N42、Feature Tags P6-N61
### 出口检查
> 能否用 CIRCUIT Core 构建双路元素炮台构建并打通 5 波?所有高级 Core 无双执行 / 空指针崩溃?**P-S5-ZM-01** 与 **P-S5-AI-01** 压力测试通过(或 ADR-A4 已更新为 FlowField 且复测通过)?"是"方可进入 S6。
>
> **状态(2026-06-05**:✅ 功能验收 P-S5-01~07 + **P-S5-AI-01**(≈0.0496msADR-A4 定案 NavigationAgent2D)全部实测通过,高级 Core 无双执行/崩溃。⏸ **P-S5-ZM-01**C# `ZoneManagerCs` 压力)按 R-08 与 BulletManagerCs 等 C# 热路径一并顺延至专门优化轮——功能 GDScript 路径已通过 P-S5-04。
> **集成(2026-06-05 完成)**:S5 系统已接入实际游戏流程并实测可玩——
> - 商店池 = `SpellRegistry.get_all_ids()`,已含全部 S5 法术(LOGIC / summon / zone / 共鸣输入 water_wave·chain_bolt)。
> - `combat_manager` 新增 **Core 切换**(商店内「法杖」按钮循环 `wand_basic→fast→memory→matrix_board→circuit_fork`),每 Core 载入演示默认 Deck`install_spell` 对 MATRIX/CIRCUIT 改为**位置化填空槽**`_rebuild_wand` 保留 `""` 空槽为 null。HUD/商店显示当前 Core 名。
> - 实测:5 Core 循环切换均正确编译(记忆法杖 every_3 跨帧 `[0,0,1,0]`、矩阵板邻接、分叉回路双弹);共鸣经商店装配触发;summon/zone 作为首动作或经 `double_cast` 正常产出。
> - **背包(2026-06-05 完成)**:点选式插槽编辑器(商店「🎒 背包」按钮),拓扑感知插槽网格 + 备牌区,支持插槽交换 / 移入移出备牌 / 即时重编译(详见 S3 出口检查段)。玩家现可在任意 Core 上自由排布法术(含 MATRIX 行 A/B、CIRCUIT 槽位、共鸣相邻)。
> - **法杖存档(2026-06-05 完成)**Core/deck/bench 已纳入 `ProfileManager` A/B 双槽(`schema_version` 升至 **2**)。`CombatManager` 经 `ProfileManager.set_wand_provider(self)` 注册,提供 `get_wand_save_data`/`apply_wand_save_data`(字段 `core_id`/`deck`/`bench`);`_collect_run_data` 写入 `wand``apply_run` 恢复。实测:自定义矩阵 Deck 存档→擦除→读档完整恢复;v1 旧档(无 wand)经 `_migrate_run` 升 v2 不崩溃保留默认;备牌持久化;A/B 交替写入保持。
> - 余项(归 S6):真·拖拽手感、CIRCUIT 有向边可视化编辑器、Steam Cloud 批量写(ST-50)。
---
## S6 — 润色与发布 (Polish & Launch)
### 目标
> **"60fps 帧率稳定(含 Profiler 数据)、20 波内容完整、手感丝滑、Endless 排行榜可用。"**
### 范围(优先级排序)
**P0 必须完成**
- [x] **完整游戏外壳(启动→主菜单→游戏→暂停→结算→返回)***2026-06-05:商业级完整流程闭环。新增 `SceneManager`Autoload,黑幕淡入淡出转场 + `start_mode` new/continue 路由)、`scenes/ui/splash.tscn`(启动画面:工作室名+游戏标题+版权,1.8s 自动/任意键跳过)、`scenes/ui/main_menu.tscn`(主菜单:新游戏/继续[无存档时禁用]/排行榜/设置/退出 + 语言切换 + 版本号);`main_scene` 改为 `splash.tscn``combat_s2.gd` 接入 SceneManager 启动模式(新游戏=`start_game`/继续=`resume_game`)、新增暂停菜单(ESC`process_mode=ALWAYS` 暂停时仍响应,继续/设置/返回主菜单/退出)、结算屏加「返回主菜单」按钮。全流程实测通过:splash→menu 自动跳转、新游戏/继续读档(恢复 wave=7/gold=250)进战斗、ESC 暂停冻结世界、死亡结算→返回主菜单(死亡删档使继续按钮禁用)。修复 2 bug:转场 Tween 竞争(fade_in_first kill 复位回调致 is_transitioning 卡死)、暂停 process_mode 致 ESC/按钮失效。**待补**:主菜单/splash 美术背景、FTUE 首启难度引导*
- [x] **程序化美术占位(视觉升级)***2026-06-05:从纯色几何方块升级为完整 bullet-heaven 视觉。新增 `shaders/bullet_glow.gdshader`(子弹加法混合发光圆:软核+外发光晕)、`shaders/enemy_circle.gdshader`(敌人体积感实体球:软边+左上高光+暗边描线)、`shaders/arena_grid.gdshader`(竞技场暗紫网格地面:细/粗双层网格+径向暗角)应用于两个 MMI + 背景 Polygon2Dz=-100);玩家标记升级为发光菱形(青光晕环+橙菱形+白核);`vfx_manager.gd` `_instantiate_vfx` 改为 `GPUParticles2D`(程序化爆裂粒子:球形发射+颜色渐变淡出+按特效 amount/speed`one_shot` P6-N72`play()` 触发 `restart`);`assets/ui/ui_theme.tres` 全局 UI Theme(按钮圆角6px+蓝紫边框+悬停高亮+禁用态,经 `gui/theme/custom` 全局应用所有 Control)。实测:150 弹同心圆弹幕发光叠加、Boss 体积球、网格地面、圆角按钮均渲染正确,0 报错。**全部为程序化生成,放真实素材(`res://audio/sfx/`、精灵 Atlas、VFX 场景)即整体替换,见 handbook/03**
- [ ] 性能验收:确认所有 C# 热路径模块(`BulletManagerCs` / `EnemyManagerCs` / `SpatialGridCs` / `SpellEvaluatorCs` / `StatusManagerCs` / `ZoneManagerCs`)均满足各自帧预算(Profiler 实测);若发现遗漏的 GDScript 瓶颈,按 ADR-L1 规则迁入 C#(`architecture_design.md §6.1`
- [~] 三档渲染切换逻辑:实现 Area2D → Dirty Sync → MultiMeshInstance2D 渐进切换(`BATCH_DISABLE_PER_FRAME=100`,回滞阈值 `<1500``architecture_design.md §4.4`)— *2026-06-05**直接实现 MultiMeshInstance2D 渲染(§4.4 最高档)**——此前子弹/敌人完全无渲染(仅 HUD+VFX 色块),现 `BulletManager.sync_multimesh`/`EnemyManager.sync_multimesh`SoA 索引↔instance 索引共享,`visible_instance_count` 控制数量)+ 单位 QuadMesh + 敌人 per-instance 颜色/尺寸(Boss 大且醒目);`combat_s2.gd` 建子弹/敌人两个 MMI + 玩家标记 + 跟随 Camera2D`_process` 每帧同步。实测渲染正确(红杂兵/品红 Boss/黄子弹)。**Area2D↔MultiMesh 三档切换 N/A**:碰撞自 S1 即为 SpatialGrid 唯一路径(从未建 Area2D),故 MultiMesh 单档覆盖全密度即可,无需 Area2D 档与回滞切换。待补(润色):Dirty Sync 视口剔除、子弹按 type 出图集、C# 化 transform 上传*
- [~] Wave 1~20 完整内容(敌人配置表、Mini Boss Wave 8、Final Boss Wave 20)(`game_design.md §3.3`)— *核心已实现 2026-06-05`wave_manager.gd` 20 波配置表(W1-5 数值 / W6-9 护甲 / W11-19 弹幕量增 / W15+ 精英寻路);敌人原型 `EnemyManager.Type`BASIC/FAST/ARMORED/ELITE/MINIBOSS/BOSS+ 按型速度/护甲;Mini Boss W8450HP)、Final Boss W201800HP+ 阶段系统(HP 66%/33% 进阶召援军)+ `BOSS_SPAWNED`Boss 死亡即通关本波。Endless 数值膨胀。实测全部通过。**待补(润色)**:Boss 弹幕攻势(Phase2 走位)、元素抗性/反弹护盾怪、敌人 .tres 资源化、Boss JSON 驱动*
- [ ] 20 张+ 法术卡(含所有 Tier 1/2/3
- [ ] **至少 7 种 Core** 可玩:`wand_basic``wand_fast``staff_long``matrix_board``circuit_fork``wand_memory``wand_eternal`(名称与数值以 `resources/cores/` 为准;与「5 种」旧表述对齐为 **7 种变体**
- [x] 死亡流程:结算屏、Run 截图(`get_viewport().get_texture().get_image()`,死亡动画第 1 帧后执行)— *2026-06-05`combat_s2.gd` 结算屏(到达波次/击杀/用时/评分 + 排行榜 + 「再来一局」);`_save_run_screenshot()` 存 `user://runs/run_*.png`(结算屏非黑屏,P-S6-04 通过)。注:战场在 GAME_OVER 前已被 `_on_player_died` 重置,故截图为结算摘要屏而非死亡瞬间战场*
- [x] Endless 模式 + 本地排行榜(波次主排名、同波次按时间升序;评分公式:`score = wave × 100000 + (86400 - elapsed_sec)`P6-N19;本地存储于 `user://endless_records.json`)— *2026-06-05`endless_records.gd`Autoload `EndlessRecords`HMAC-SHA256 签名(ST-40+ 读取验签拒绝篡改(ST-41);排序波次降序/同波次用时升序;`compute_score` 实测 786386 正确。**修正一处真实 bug**:原按对象签名会因 JSON int↔float 往返破坏验签,改为对精确 JSON 字符串签名*
**P1 完成(时间允许)**
- [ ] `DUAL_STREAM` Core feature 实现(奇数槽中间槽前流专属,P6-N12)
- [ ] `ALWAYS_CAST_LAST`:尾槽为 MODIFIER/TRIGGER/LOGIC 时静默跳过(P6-N7
- [ ] `SHUFFLE_DECK`:ACTION 原子单元整体乱序(P5-N2
- [ ] `INFINITE_SPELLS`:配合 `heavy_cost` 的卖血流(P6-N18
- [ ] 敌人元素弱点 + 抗性 + 催化反应(StatusManager 交互矩阵)
- [x] 音频系统(32 AudioStreamPlayer2D 池、0.1s 节流)— *2026-06-05`audio_manager.gd`Autoload `AudioManager`32 池 round-robin + 每 sound_id 0.1s 节流 + 运行时建 SFX 总线 send→Master(受 SettingsManager 音量);EventBus 挂钩(ENEMY_KILLED/BULLET_HIT/PLAYER_DAMAGED/WAVE_COMPLETE/BOSS_SPAWNED/SHOP_OPENED/LEVEL_UP);无素材时程序化蜂鸣占位(放 `res://audio/sfx/<id>.ogg` 即替换)。实测池/节流/独立 id/未知 id 优雅 no-op/事件触发通过。**待补**:真实音效素材、BGM 分层*
- [ ] 移动端适配(虚拟摇杆映射到 InputMap Action
- [ ] 完整图鉴系统(法术、Core、共鸣配方发现机制 P5-C2)
- [x] 难度系统:提供初学者 / 标准 / 挑战三档(初学者:敌人 HP ×0.7、伤害 ×0.7;挑战:W20 Boss HP ×1.3、Wave 怪物数 ×1.2);难度选项在首次启动时通过 FTUE 引导选择,后续可在主菜单修改;难度配置持久化到 `user://save_data.json``difficulty: int`0=初学者、1=标准、2=挑战)— *2026-06-05`settings_manager.gd`Autoload `SettingsManager`)三档难度 + 乘子(`enemy_hp_mult`/`player_dmg_taken_mult`/`wave_count_mult`/`boss_hp_mult`),接入 `PlayerStats.take_damage`(初学者 20→14 实测)、`WaveManager` 怪物数/血量/Boss 血(挑战 W1 20→24 实测);持久化 `save_data.json`difficulty/locale/master_volume/ftue_done)。设置面板(难度/语言/音量滑条/清除数据)实测渲染。**FTUE 首启难度引导已完成 2026-06-05**:首次启动(`save_data.json``ftue_done`)点「新游戏」弹出三档难度选择界面(初学者/标准/挑战,颜色编码绿/蓝/红 + 各档说明文字 + 「设置中可改」提示);选定后 `SettingsManager.complete_ftue()` 设难度+标记+存档→进游戏;二次启动不再引导;ST-03 清除数据后重新触发。实测全通*
- [x] 设置菜单 + 音量(Master 总线)+ 「清除所有本地数据」入口(ST-03)— *2026-06-05`combat_s2.gd` 设置面板(商店「⚙ 设置」按钮打开);音量 HSlider→`AudioServer` Master 总线;`SettingsManager.clear_all_local_data()` 实测删除 save_data/endless_records/run_a·b/runs 目录并重置*
**P2 可选高级内容**
- [ ] `LOGIC_LABEL / LOGIC_JUMP_IF`(寄存器条件跳转,仅限 DEBUG Core 解锁)
- [ ] `LogicHandler` 接口(LOGIC 指令集 > 10 条时替换 match 硬编码,P6-N4 规范)
- [ ] `anywhere_in_deck` O(N²) 共鸣扫描(仅传说配方)
- [ ] SpellEvaluator 调试图(EditorPlugin 单步执行,Q2 预案)
- [ ] `ResourceLoader.load_threaded_request` 异步预加载(SpellRegistry P1 路线图)
### 验收标准 (P0)
- [~] **P-S6-01**:Wave 1~20 无崩溃完整通关;Mini Boss Wave 8 及 Final Boss Wave 20 AI 正常(`game_design.md §3.3`)— *系统已实现并实测:20 波配置全部正确生成(杂兵/护甲/精英寻路计数核对);W8/W20 Boss 入场(450/1800 HP+ 三阶段进阶(60%→P2、30%→P3 各召援军)+ `BOSS_SPAWNED`;Boss 死亡通关、普通波清空通关;护甲减伤(ARMORED 20→15)。完整 20 波连续通关压力测试待 S6 后续连跑*
- [x] **P-S6-02**:同屏 500 敌人 + 1500 子弹稳定 60fpsProfiler 实测)— *实测通过 2026-06-05RTX 2060):**实时 FPS=180**`_physics_process` 实测帧时间 **0.91ms**;手工逐系统计时(60 reps):Bullet 22.95μs + Enemy 396.97μs + SpatialGrid 0.15μs + Status 0.23μs + Zone 0.18μs + MultiMesh sync 180.10μs = **总计 0.601ms = 16.67ms 预算的 3.6%**。全部 GDScript 回退路径(无 C# 热化),仍有极大裕量*
- [x] **P-S6-03**:Endless 模式可进入;本地排行榜以波次为主排名、同波次按时间升序排(P6-N19 权威公式:在线分 = `wave × 100000 + (86400 - elapsed_sec)`;本地存储于 `user://endless_records.json`)— *实测 2026-06-05:排序 W12/250→W12/300→W8/90→W5/120;评分 786386 正确;HMAC 签名/验签/篡改拒绝通过。注:Endless「继续」入口(W20 后)属玩法接线,待补*
- [x] **P-S6-04**:死亡时截图正确保存到 `user://runs/`(不截到黑屏)— *实测 2026-06-05`user://runs/run_*.png` 已生成(结算屏内容,非黑屏)*
- [x] **P-S6-05**:所有 P6-N 规范条目已在实际代码中验证(无违反)— *2026-06-05 审计通过(12 条约束)。发现并修复 1 件真实违反:`spell_evaluator.gd:504` `_acquire_ctx()` 在热路径 `execute_compiled`→`_physics_process` 中懒分配 `SpellContext.new()`(违反 P6-N3)。修复:在非热路径的 `compile_wand()` 阶段检测 `PERSISTENT_MEMORY` feature tag 时**预热分配** SpellContext`_persistent_ctx[0]`),确保后续施法 `_acquire_ctx()` 走 dict.get() 取已有实例,不再触发 `new()`。P-S5-03 回归测试通过 `[0,0,1,0]`。其余 11 条全 PASSENEMY_STRIDE、BULLET_STRIDE、StatusID、compile_wand 参数序、zone_manager while+=、vfx one_shot、PackedByteArray int()、CoreFeatureTag、registers 初始化、EventID 序列、ADR-A2 存档)*
- [~] **P-S6-06**:本地化与 **ADR-A1** 对齐:`translations/` 下至少存在 **`zh_CN.po``zh_TW.po`(繁体)、`en.po``ja.po`** 四个文件(键集一致;`zh_TW`/`ja` 可先用占位译文,但商店页承诺的语言须在发售前填满);所有 S1–S6 玩家可见字符串均为 `tr("KEY")`(调试 `push_warning`/`push_error` 除外);业务脚本无裸中文字符串(以项目约定之静态检查或人工审计为准)— *2026-06-05 骨架完成:`translations/` 四个 `.po` 全部就位且**键集一致(实测 4 语言 0 缺键)**,全部填了真实译文(非占位);`Locale` Autoload 经 `TranslationServer.add_translation` 加载 + 切换;`combat_s2.gd` HUD/商店/结算/背包**主 UI 字符串已全部 `tr("KEY")` 化**(实测 en/ja/zh_TW 切换正确);商店内加语言切换按钮。**待补**:Core/法术/状态/Boss 的 `display_name`/`description`(现为 `WandPreset`/各 Def 内硬编码,需键化为 `SPELL_*`/`CORE_*`/`STATUS_*` 并补四语);裸中文静态检查脚本*
- [x] **P-S6-07**:内存峰值实测(Wave 20 Boss 战场景):PC 端 RSS < 512MB,对照 `architecture_design.md §4.6` 内存预算表各类别实测均不超标 — *实测通过 2026-06-05W20 满载:500敌+2000弹+500状态+32 Zone):`MEMORY_STATIC`=105.9MB + 引擎基础≈70MB → **估算 RSS≈175.9MB34%,目标<512MB ✓)**;热数据(全 SoA+MultiMesh)仅 0.3MBVRAM=92.6MB 为 GPU 显存不计入 PC RSS。Switch 共享内存(VRAM+RAM≈268MB)低于 SW-11 的 2.5GB 限额,实机测量待 Switch 移植时确认*
---
## 三、时间线估算 (Timeline Estimate)
> 以"单人 + 2D 美术外包"为基准估算,仅供参考,实际根据团队调整。
| 切片 | 状态 | 估算工时 | 主要阻塞风险 |
| :--- | :---: | :--- | :--- |
| **S0 技术骨架** | ✅ | 1~2 周 | R-01/R-03 已缓解(GDScript 回退路径) |
| **S1 最小战斗** | ✅ | 1 周 | — |
| **S2 核心循环** | ✅ | 1~2 周 | — |
| **S3 法术管道** | ✅ | 2~3 周 | — |
| **S4 战斗深度** | ✅ | 1~2 周 | — |
| **S5 高级构建** | ✅* | 3~4 周 | 功能完成(*C# 压力 P-S5-ZM-01 顺延);_flatten_circuit 嵌套分叉已验、MinionManager AI 已验 |
| **S6 润色发布** | ⬜ | 4~6 周 | 内容量(法术/敌人设计);平衡调整 |
| **合计** | — | **~14~20 周** | R-08 C# 热路径迁移若触发则追加 1~2 周 |
---
## 四、跨切片技术约束速查 (Cross-Slice Constraints)
以下约束贯穿所有切片,违反时导致回归风险,开发时随时核查:
| 约束 | 来源规范 | 高风险操作 |
| :--- | :--- | :--- |
| 禁止 `DamageContext` / `SpellContext` 在战斗循环中 `new()` | P6-N49、P6-N3 | 任何 `apply_damage` 调用路径 |
| `DamageContextPool.acquire()` 必须先 `ctx.reset()` | P6-N67 | 任何新增 `acquire` 调用 |
| `StatusType.XXX` 枚举不存在,必须用 `StatusID.XXX` | P6-N30 | 新增状态效果代码 |
| `CoreFeatureTag.XXX` 常量,禁止裸字符串 | ADR-R5-N2 | Feature Tag 判断逻辑 |
| `ENEMY_STRIDE=8` 命名常量(禁止裸整数 8 | P6-N54 | EnemyManager 所有 SoA 访问 |
| `BULLET_STRIDE=12` 命名常量(禁止裸整数 12 | `architecture_design.md §4.2` | BulletManager 所有 SoA 访问 |
| `SpellContext.registers` 必须初始化长度 4 | P6-N48 | SpellContext 对象池 reset() |
| `compile_wand(core, raw_deck)` 参数顺序 core 在前 | P6-N70 | SpellEvaluator 入口调用 |
| ZoneManager tick 用 `while + -=`,不用 `if + =0` | P6-N71 | ZoneManager 任何 tick 逻辑 |
| `VFXManager._instantiate_vfx` 必须 `one_shot=true` | P6-N72 | VFX 节点创建路径 |
| `CHARGE_FIRED`(ID=14) 一次性;SpellEvaluator 订阅此而非 `CHARGE_STATE_CHANGED` | P6-N69 | 蓄力法术实现 |
| `SubPayloadRegistry` 仅在关卡进行中只读,背包关闭后重编译 | P6-N10 | 任何支持"战斗中改装备"的功能 |
| Run 存档 **A/B 双槽**`schema_version`、**`_migrate_run` 链** | ADR-A2 | 单槽覆写、跳过迁移、损坏全丢 |
| `EventID` **121**`implementation_plan.md` §2.1 表一致(含 `ACHIEVEMENT_UNLOCKED=18``GAME_STATE_CHANGED=20``SPELL_DROP_PICKUP=21`);新增事件从 22 起递增并向 §2.1 表登记 | E-N1、certification ST-10、boss_design §6 | 在业务代码中硬编码与表冲突的事件整数 |
| C# 内层循环(`for` / `while` 体内)禁止 `GodotObject.Call()` / `.Set()` | ADR-L1 规则 1 | `BulletManagerCs` / `EnemyManagerCs` 任何新增热路径代码 |
| C# 访问 SoA 热数组必须通过 `PackedFloat32Array.AsSpan()` 获取 `Span<float>` 再做索引 | ADR-L1 规则 2 | 任何新增 C# SoA 读写代码 |
| `PackedByteArray[i]` 赋值必须显式 `int()` 转换 | P6-N27 | `_visible_flags` 相关代码 |
| `ProjectileDef.reset()` 必须重置 `spawn_position` | P6-N41 | BulletManager spawn 路径 |
---
## 五、参考文档导航 (Reference Map)
| 需要了解 | 首要参考文档 | 章节 |
| :--- | :--- | :--- |
| 局内 Run 存档 A/B 与迁移 | `technical/architecture_design.md` | ADR-A2 |
| Elite / Boss 寻路 | `technical/architecture_design.md` | ADR-A4 |
| 全局架构与层级关系 | `technical/architecture_design.md` | §2~4 |
| SpellEvaluator 执行流 + 预编译 | `technical/architecture_design.md` | §3.4 |
| BulletManager SoA 字段定义 | `technical/architecture_design.md` | §4.2 |
| ZoneManager 完整实现 | `technical/architecture_design.md` | §8 ADR-R5-N1 |
| EventBus 事件目录 | `technical/implementation_plan.md` | §2.1 |
| DamageContext 字段权威 | `technical/implementation_plan.md` | §2.1 (P6-N49) |
| EnemyManager SoA + LOD | `technical/implementation_plan.md` | §2.3.C |
| PlayerManager 移动状态追踪 | `technical/implementation_plan.md` | §2.5.A |
| VFXManager 完整实现 | `technical/implementation_plan.md` | §2.5.D |
| DPS 环形缓冲区 | `technical/implementation_plan.md` | §2.5.E |
| Core 插槽拓扑、Feature Tags | `design/core_wand_design.md` | §1~3 |
| SpellEvaluator 两阶段接口 | `design/core_wand_design.md` | §6 |
| 数值公式(XP/伤害/商店) | `design/numerical_design.md` | §1~3 |
| 法术参数参考表 | `design/numerical_design.md` | §2.2 |
| 状态效果与 DoT | `mechanics/combat_mechanics_depth.md` | §3~4 |
| BulletContext 连锁弹射 | `mechanics/combat_mechanics_depth.md` | §2/5 |
| StatusManager Tick 机制 | `mechanics/combat_mechanics_depth.md` | §3.2.5 |
| COMBO_MARK / VULNERABILITY | `mechanics/consecutive_hits_stacking.md` | 全文 |
| MinionManager / ZoneManager | `mechanics/advanced_mechanics_summons_and_environment.md` | §1/3 |
| StatusID Autoload 规范 | `mechanics/advanced_mechanics_summons_and_environment.md` | §3.2 |
| 运动学 / 物理扩展(P2/P3) | `mechanics/combat_mechanics_extensions_v2.md` | §1~5 |
| 弹道修正器(正弦/环绕) | `mechanics/weapon_system_expansion.md` | §1 |
| 全规范速查表 (P6-N\*) | `README.md` | §关键规范速查 |