E1-③ 归航实现删除了 _enemy_pos_snapshot 与 EnemyManager.fill_pos_snapshot (锁定式目标需 entity_id,快照按槽位存位置不含 id),权威文档随之失准,一并订正: - docs/README.md:P6-N25(快照须为类成员)与 P6-N2(Homing 弹共用快照)双双失去 约束对象,改为删除线 + ⚠️ 已退役(2026-07-30) 并指向 spec §2.4/§2.7。不删条目, 避免编号规范留下悬空引用。 - architecture_design.md §4.2:homing 伪代码整段改写为实际算法——冷数据 homing_strength(弧度/秒)/homing_range/homing_target_id、发射不解析目标首帧惰性 锁定、仅目标失效时经 _find_nearest_unvisited 重选、wrapf 最短转向 + clampf 限 角速度 + rotated() 保速率、空场守卫、失败退避+抖动、bounce 协同改写目标并撤销 退避键。性能数字一律指向 spec §5,不另起一套。 - 冷数据字段表 homing_force → homing_strength/homing_range/homing_target_id。 - EnemyManager API:fill_pos_snapshot 换成实际存在的 get_pos_by_id(含哨兵约定); get_nearest_pos 与自动瞄准改为「直接扫描 SoA」,原文称与 homing 快照共用属误述。 - S0 性能表 EnemyManagerCs 行标注该实测含已删除的 fill_pos_snapshot(保留历史数字)。 - MinionManager.fill_pos_snapshot 标注为未实现提案,并纠正其「供 homing 用」的注释。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
150 lines
17 KiB
Markdown
150 lines
17 KiB
Markdown
# 魔法工匠 (Arcane Artificer) - 文档索引
|
||
|
||
> Roguelite 动作射击游戏 | Godot 4.x + GDScript
|
||
|
||
---
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
docs/ 产品文档(设计规范 / 架构参考)
|
||
README.md 本文件,文档导航索引
|
||
design/ 游戏设计文档
|
||
technical/ 技术架构文档
|
||
mechanics/ 机制细节设计
|
||
handbook/ 开发者 / 策划手册
|
||
|
||
docs_dev/ 开发过程 / 归档文档(工程根目录,见下)
|
||
development_plan.md 垂直切片开发计划
|
||
certification_checklist.md 平台认证清单
|
||
archived_cocos_architecture_draft.md 早期 Cocos 架构草案(已废弃)
|
||
```
|
||
|
||
---
|
||
|
||
## 工程计划与归档 (`../docs_dev/`)
|
||
|
||
> 开发过程/追踪与归档类文档已移出 `docs/`,统一存放于工程根目录的 [`docs_dev/`](../docs_dev/)。
|
||
|
||
| 文档 | 内容简述 |
|
||
| :--- | :--- |
|
||
| [development_plan.md](../docs_dev/development_plan.md) | S0–S6 骨架垂直切片、风险登记、验收与出口检查、跨切片约束、参考文档地图(与 `technical/` 架构对齐) |
|
||
| [certification_checklist.md](../docs_dev/certification_checklist.md) | Steam / Switch / 无障碍 / 发布前检查项,并映射到切片 |
|
||
| [archived_cocos_architecture_draft.md](../docs_dev/archived_cocos_architecture_draft.md) | 早期 Cocos Creator / TypeScript 架构草案(已废弃,权威架构见 `technical/`) |
|
||
|
||
---
|
||
|
||
## 游戏设计 (`design/`)
|
||
|
||
| 文档 | 内容简述 |
|
||
| :--- | :--- |
|
||
| [game_design.md](design/game_design.md) | 核心玩法 GDD:法术管道系统、游戏循环、构建示例、Endless 排行榜 |
|
||
| [numerical_design.md](design/numerical_design.md) | 数值策划:资源模型、XP 曲线公式、法术参数表、怪物成长曲线、Boss DPS 门槛、平衡性对策 |
|
||
| [core_wand_design.md](design/core_wand_design.md) | Core 设计:插槽拓扑、MATRIX 邻接加成效果表、Feature Tags 常量规范、dual_stream 奇数槽行为 |
|
||
|
||
---
|
||
|
||
## 技术架构 (`technical/`)
|
||
|
||
| 文档 | 内容简述 |
|
||
| :--- | :--- |
|
||
| [architecture_design.md](technical/architecture_design.md) | 系统分层架构(含 ZoneManager/VFXManager/CoreFeatureTag)、HMWS 法术系统设计、ECS-Lite、超大弹体 SpatialGrid 豁免、Feature Tags 常量 ADR、DamageType 枚举、自动瞄准算法、consume_inputs 掩码机制、技术栈选型 |
|
||
| [implementation_plan.md](technical/implementation_plan.md) | 各模块详细实现方案(含 VFXManager 正式定义)、EventBus 完整事件目录、SubPayloadRegistry 竞争条件保证、DamageContextPool 定义、PlayerManager 移动状态追踪、DPS 面板实现、开发阶段规划、关键技术难点预案 |
|
||
|
||
---
|
||
|
||
## 机制细节 (`mechanics/`)
|
||
|
||
| 文档 | 内容简述 |
|
||
| :--- | :--- |
|
||
| [combat_mechanics_depth.md](mechanics/combat_mechanics_depth.md) | 投射物溯源、连锁弹射、状态效果、伤害计算管线 |
|
||
| [combat_mechanics_extensions_v2.md](mechanics/combat_mechanics_extensions_v2.md) | 物理运动学、环境场、触发链、属性快照、Proc 系数 |
|
||
| [weapon_system_expansion.md](mechanics/weapon_system_expansion.md) | 轨迹修正器(正弦/回旋镖/环绕-双写优化)、触发器系统、元素交互 |
|
||
| [consecutive_hits_stacking.md](mechanics/consecutive_hits_stacking.md) | 连击窗口、叠加收益、Combo Tracker 数据结构设计、COMBO_MARK tick_interval 语义说明 |
|
||
| [advanced_mechanics_summons_and_environment.md](mechanics/advanced_mechanics_summons_and_environment.md) | 召唤体系(炮台/随从/卫星)、环境传播、地面效果、StatusID Autoload 常量规范 |
|
||
|
||
---
|
||
|
||
## 核心设计理念
|
||
|
||
- **"Noita 遇上 Brotato"**:快节奏割草 × 深度法术编程
|
||
- **法术管道 (Spell Pipeline)**:ACTION → MODIFIER → TRIGGER → LOGIC GATE 四类节点组合,实现图灵完备的武器逻辑
|
||
- **数值哲学**:`构建强度 = (基础数值 × 修正系数) ^ 逻辑复杂度`,提供宽广边界而非限制玩家策略
|
||
|
||
---
|
||
|
||
## 关键规范速查 (Design Rules Quick Reference)
|
||
|
||
| 规范 ID | 位置 | 内容摘要 |
|
||
| :--- | :--- | :--- |
|
||
| P6-N2 | architecture_design.md §4.2 | ~~Homing 弹共用敌人位置快照~~ ⚠️ **已退役(2026-07-30)**:归航改为**按 entity_id 锁定目标**,目标有效时零查询、仅失效时重选,不再需要共用快照。见 spec [`2026-07-30-bullet-homing-design.md`](../docs_dev/specs/2026-07-30-bullet-homing-design.md) §2.4 / §2.7 |
|
||
| P6-N3 | architecture_design.md §4.1 | SpellContext 池大小 = 32 |
|
||
| P6-N5 | implementation_plan.md §2.2 | CompiledDeck 多项式滚动哈希(顺序敏感,种子含 grid_cols,防换位误判) |
|
||
| P6-N6 | implementation_plan.md §2.1 | EventID 12 MINION_EXPIRED |
|
||
| P6-N10 | implementation_plan.md §2.2 | SubPayloadRegistry 竞争条件安全保证 |
|
||
| P6-N11 | architecture_design.md §4.3 | 超大弹体(radius>64px)豁免 SpatialGrid |
|
||
| P6-N12 | core_wand_design.md §3 | dual_stream 奇数槽前流多得中间槽 |
|
||
| P6-N13 | core_wand_design.md §2.2 | MATRIX 邻接加成效果表 |
|
||
| P6-N14 | consecutive_hits_stacking.md | COMBO_MARK tick_interval 语义重载说明 |
|
||
| P6-N15 | weapon_system_expansion.md §1.3 | 环绕弹先判断 is_orbiting 再决定是否积分 |
|
||
| P6-N16 | numerical_design.md §1.2 | XP 曲线:`⌊10 × 1.4^(level-1)⌋` |
|
||
| P6-N17 | numerical_design.md §5.D | W20 Boss 物理构建 DPS 门槛 |
|
||
| P6-N18 | numerical_design.md §5.E | infinite_spells + heavy_cost 叠加规则 |
|
||
| P6-N19 | game_design.md §8.2 | Endless 排行榜单整数双维度评分方案 |
|
||
| P6-N20 | architecture_design.md §3.4.A | _flatten_matrix 仅遍历 Row A,Row B 不追加为执行节点 |
|
||
| P6-N21 | architecture_design.md §8 ADR-R5-N1 | ZoneManager 首元素删除用循环前移,不用 remove_at() |
|
||
| P6-N22 | numerical_design.md §1.2 | XP 可行性验证:Level 20 为 20 波通关上限,Level 21+ 服务 Endless |
|
||
| P6-N23 | numerical_design.md §1.2 | 商店刷新费用公式:20 + (本波刷新次数 × 10)G,波次结束重置 |
|
||
| P6-N24 | architecture_design.md §4.2 | ProjectileDef(生成时模板)vs _bullet_contexts(运行时可变冷状态)职责分离 |
|
||
| P6-N25 | architecture_design.md §4.2 | ~~_enemy_pos_snapshot 必须声明为类成员(预分配复用,禁止每帧 var 分配)~~ ⚠️ **已退役(2026-07-30)**:约束对象 `_enemy_pos_snapshot` 已删除 —— 归航改用**按 entity_id 锁定目标**,而该快照按槽位存位置、不含 entity_id,支撑不了锁定语义。见 spec [`2026-07-30-bullet-homing-design.md`](../docs_dev/specs/2026-07-30-bullet-homing-design.md) §2.7 |
|
||
| P6-N26 | architecture_design.md §8 ADR-R5-N1 | ZoneManager ZONE_STRIDE=8(含 tick_interval+tick_accum),spawn_zone 需 tick_interval 参数 |
|
||
| P6-N27 | implementation_plan.md §2.3.C | _visible_flags 类成员 PackedByteArray;has_point() 赋值须显式 int() 转换 |
|
||
| P6-N28 | implementation_plan.md §2.1 | DamageContextPool Autoload 定义(acquire/get_context/release) |
|
||
| P6-N29 | architecture_design.md §3.4.C | consume_inputs 运行时通过 _consumed 掩码实现,CompiledDeck 只读不修改 |
|
||
| P6-N30 | advanced_mechanics §3.2 | StatusID Autoload 常量规范(禁止 StatusType.XXX 枚举写法) |
|
||
| P6-N31 | architecture_design.md §3.2 | ProjectileDef.reset() 必须用 DamageType.PHYSICAL(禁止裸整数 0) |
|
||
| P6-N32 | architecture_design.md §3.2 | SpellContext 必须提供 reset() 方法;registers 不在 reset() 中清零(由 SpellEvaluator 按持久化策略决定) |
|
||
| P6-N33 | architecture_design.md §3.4.A | _flatten_circuit() 正式算法:Kahn 拓扑排序 + splitter 展开为 SubPayload LOGIC_FORK;环路时返回空 CompiledDeck |
|
||
| P6-N34 | combat_mechanics_depth.md §5 | BulletContext 禁止持有 calc_damage() 等伤害计算逻辑(SRP);弹射衰减由 EnemyManager.apply_damage() 用 bounce_count 计算 |
|
||
| P6-N35 | combat_mechanics_depth.md §4 / implementation_plan.md §2.1 | DamageContext 权威字段:base_damage/**mult**/damage_type/owner_id/source_tags/is_crit/pierce_rate;DamageContextPool.acquire() 含 is_crit+pierce_rate 参数;`reset()` 必须重置 mult=1.0 |
|
||
| P6-N36 | implementation_plan.md §2.1 | EventID.DASH_TRIGGERED = 13;负载: caster_id+stationary_time;发出方 PlayerManager,订阅方 SpellEvaluator+UIManager |
|
||
| P6-N37 | implementation_plan.md §2.2 | LOGIC_EVERY_N_SHOTS 必须读写 SpellContext.registers(跨帧持久),严禁使用 CastState.registers(每帧重置) |
|
||
| P6-N38 | implementation_plan.md §2.5.E | DPS 环形缓冲区:PackedFloat64Array+PackedFloat32Array,_RB_SIZE=256,O(1) 写入,消除 pop_front() O(N) 移位 |
|
||
| P6-N39 | consecutive_hits_stacking.md / advanced_mechanics §3.2 | StatusID.VULNERABILITY=8,StatusID.COMBO_MARK=7;新增 ID 从 9 开始递增 |
|
||
| P6-N40 | architecture_design.md §3.2 | SpellContext.stats 必须声明时初始化(= CastStats.new()),防止 reset() 空指针 |
|
||
| P6-N41 | architecture_design.md §3.2 | ProjectileDef.reset() 必须重置 spawn_position(防止池化复用携带上帧发射原点) |
|
||
| P6-N42 | core_wand_design.md §1 | CoreDefinition 新增 @export var edges: Array = [](CIRCUIT 有向边顶层字段);_flatten_circuit() 从 core.edges 读取,不在 slot_configs 内嵌 "edges" |
|
||
| P6-N43 | architecture_design.md §3.4.A | _flatten_circuit() 降级为 LINEAR 时使用 CompiledDeck.new(deck.nodes) 内联(_flatten_linear() 函数不存在) |
|
||
| P6-N44 | architecture_design.md §3.4.A | LOGIC_FORK 节点:type=LOGIC, id="LOGIC_FORK",meta "fork_branch_ids";_execute_logic 并行触发所有分支 SubPayload |
|
||
| P6-N45 | implementation_plan.md §2.3.C | ENEMY_STRIDE: int = 8 命名常量,与 BULLET_STRIDE=12 规范对齐 |
|
||
| P6-N46 | architecture_design.md §8 ADR-R5-N1 | ZoneManager swap-and-pop 移除时必须调用 VFXManager.play("zone_expire", ...) |
|
||
| P6-N47 | implementation_plan.md §2.3.A | MOVE_THRESHOLD_NORM = 0.067(归一化输入幅度,非像素/秒);DPS get_dps() 懒加载 _recalc_window,record_damage() 仅做 O(1) 写入 |
|
||
| P6-N48 | architecture_design.md §3.2 | SpellContext.registers 必须声明时初始化 `= PackedFloat32Array([0.0, 0.0, 0.0, 0.0])`(长度 4);未初始化则 registers[0] 触发 out-of-bounds 崩溃,fill(0.0) 对空数组无操作 |
|
||
| P6-N49 | implementation_plan.md §2.1 | DamageContext 必须独立为 damage_context.gd(class_name DamageContext);DamageContextPool 为独立 Autoload 文件,不声明 class_name;两者混于同一文件会导致 _pool: Array[DamageContext] 存入具有管理方法的池管理器实例(语义错误) |
|
||
| P6-N50 | numerical_design.md §2.2 | Spell Database 新增 damage_type 列;Projectile 类型法术必须显式指定 DamageType(如 chain_bolt=LIGHTNING, nuke=PHYSICAL);Modifier/Trigger/Multicast 填 — |
|
||
| P6-N51 | architecture_design.md §3.2 | SpellNode.type 声明为 `SpellType = SpellType.ACTION`(枚举类型注解);原 `int` 类型丢失编译期检查 |
|
||
| P6-N52 | architecture_design.md §3.4.A | `_flatten_circuit()` LOGIC_FORK 构造:每条出边必须单独 `SubPayloadRegistry.register([entry_node])`,禁止将所有分支入口节点合并到同一 SubPayload(否则并行分支退化为顺序执行) |
|
||
| P6-N53 | architecture_design.md §3.2 | SpellNode 新增 `element_tags: Array[String] = []`;共鸣系统 `_check_resonance()` 通过此字段匹配 `resonance_recipes.json` 中 `pattern` 数组的 `"tag:xxx"` 元素 |
|
||
| P6-N54 | implementation_plan.md §2.3.C | EnemyManager LOD 循环必须使用 `ENEMY_STRIDE`(= 8),禁止使用旧常量名 `STRIDE` |
|
||
| P6-N55 | core_wand_design.md §6 | `SpellEvaluator` 采用两阶段架构:`compile_wand()` 编译(法杖装备/换牌时) + `execute_compiled()` 执行(每次施法);`_flatten_circuit()` 必须传 `core.edges`,不是 `core.slot_configs` |
|
||
| P6-N56 | implementation_plan.md §2.2 | `SpellDeck.pop()` 简化版须含 `null` 安全边界;权威完整实现(含 `_consumed` 掩码)见 `architecture_design.md §3.4.C` |
|
||
| P6-N57 | architecture_design.md §3.4.A | `_flatten_circuit()` 分叉检测须用出度(`adj[slot_idx].size() > 1`),禁止依赖 "splitter" tag(tag 漏写时静默失效);"splitter" tag 仅保留为 UI 标注 |
|
||
| P6-N58 | architecture_design.md §3.4.A | LOGIC_FORK SubPayload 须用 `_collect_branch_path()` 收集完整分支路径(多节点链),禁止仅注册入口节点(否则后续节点全被丢弃);分叉节点自身法术先追加再插入 LOGIC_FORK |
|
||
| P6-N59 | implementation_plan.md §2.5.A | `DASH_TRIGGERED` 事件负载须包含 `stationary_time: float`(P6-N36 要求),代码中 `EventBus.emit` 必须同时传 `caster_id + stationary_time`,冲刺后立即清零 `_stationary_time` |
|
||
| P6-N60 | implementation_plan.md §2.5.D | VFXManager 必须维护 `_active_count: int` 计数器;`_pool_pop()` 时 +1,`_pool_return()` 时 -1;`play()` 中以 O(1) 方式检查上限,禁止遍历 pool 字典统计(O(N)) |
|
||
| P6-N61 | core_wand_design.md §6 | `execute_compiled(compiled, ctx, core)` 必须显式接收 `core: CoreDefinition` 参数;通过 `core.feature_tags` 判断持久内存策略;禁止使用 `ctx.core_feature_tags`(SpellContext 无此字段) |
|
||
| P6-N62 | combat_mechanics_depth.md §4 | DamageContext 新增 `mult: float = 1.0` 字段(伤害乘算系数);`reset()` 必须重置 `mult = 1.0`,否则池化复用时暴击系数残留 |
|
||
| P6-N63 | architecture_design.md §3.4.A | `_collect_branch_path()` 的 `in_degree` 参数必须传 Kahn 循环**前**的原始入度副本(`orig_in_degree = in_degree.duplicate()`,在建立 adj 表之后、Kahn BFS 之前);Kahn 结束后工作数组全为 0,用其做 `in_degree[nxt] <= 1` 汇聚检测恒成立,导致分支路径越界纳入主链节点 |
|
||
| P6-N64 | architecture_design.md §3.4.A | `_flatten_circuit()` 必须维护 `in_branch_payload: Dictionary`,`_collect_branch_path()` 每访问一个槽索引即写入;主链 `topo_order` 循环在追加节点前检查此字典并跳过,防止分支节点在 SubPayload(LOGIC_FORK 路径)和主链(topo_order 直接追加)中各执行一次 |
|
||
| P6-N65 | core_wand_design.md §6 | `compile_wand()` 参数类型修正:`slot_spells: Array[SpellNode]` → `raw_deck: SpellDeck`;`_flatten_matrix()` 与 `_flatten_circuit()` 均通过 `deck.nodes[i]` 访问节点,传入裸 Array 会触发"Invalid get index 'nodes'"运行时错误;LINEAR 路径同步改为 `CompiledDeck.new(raw_deck.nodes)` |
|
||
| P6-N66 | architecture_design.md §3.4.A | `_collect_branch_path()` 内若遇到嵌套分叉(`adj[cur].size() > 1`),必须递归为每条子边调用自身并注册 SubPayload,再注入嵌套 LOGIC_FORK 节点后 `break`;直接 `break` 会丢弃子分支全部节点 |
|
||
| P6-N67 | implementation_plan.md §2.1 | `DamageContextPool.acquire()` 必须在赋值任何字段前先调用 `ctx.reset()`,防止池化复用时残留 `source_tags`(如 TRIGGERED=2)污染新的伤害上下文 |
|
||
| P6-N68 | mechanics/consecutive_hits_stacking.md | `StatusTypeDef.can_catalyze` 类型为 `Array[int]`;`COMBO_MARK` 的正确值为 `[]`(空数组),写 `false`(bool)在 GDScript 4 严格类型模式下触发类型赋值错误 |
|
||
| P6-N69 | implementation_plan.md §2.5.A | 停步蓄力采用边沿检测:PlayerManager 维护 `_charge_triggered: bool`;首次 `_stationary_time >= CHARGE_TRIGGER_TIME` 时置 `true` 并发出 `CHARGE_FIRED`(ID=14,一次性);移动/冲刺后重置为 `false`。`SpellEvaluator` 订阅 `CHARGE_FIRED`,**禁止**订阅 `CHARGE_STATE_CHANGED`(后者每帧 60Hz 发送,订阅将导致法术每帧重复释放) |
|
||
| P6-N70 | architecture_design.md §3.4.A | SpellEvaluator 预编译入口必须命名为 `compile_wand(core: CoreDefinition, raw_deck: SpellDeck)`,权威定义见 `core_wand_design.md §6` |
|
||
| P6-N71 | architecture_design.md §8 ADR-R5-N1 | ZoneManager tick 累积器必须使用 `while + -= tick_interval`(同 StatusManager 模式),防止大帧余量丢失。`SpatialGrid.query_circle` 仅调用一次,置于 if 守卫内、while 循环外,避免重复空间查询 |
|
||
| P6-N72 | implementation_plan.md §2.5.D | VFXManager `_instantiate_vfx` 必须设置 `node.one_shot = true`;否则 `GPUParticles2D` 无限循环,`finished` 信号永不触发,`_pool_return` 永远不被调用,`_active_count` 只增不减。节点挂载到 VFXManager Autoload 自身,跨场景持久存活 |
|
||
| ADR-R5-N1 | architecture_design.md §8 | ZoneManager 架构定义(PackedFloat32Array SoA,ZONE_STRIDE=8,MAX_ZONES=64,无 Area2D) |
|
||
| ADR-R5-N2 | architecture_design.md §8 | Feature Tags 字符串常量规范(CoreFeatureTag Autoload) |
|
||
| V-N1 | implementation_plan.md §2.5.D | VFXManager MAX_ACTIVE_VFX = 200 |
|