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

142 lines
15 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) - 文档索引
> Roguelite 动作射击游戏 | Godot 4.x + GDScript
---
## 目录结构
```
docs/
README.md 本文件,文档导航索引
design/ 游戏设计文档
technical/ 技术架构文档
mechanics/ 机制细节设计
plan/ 垂直切片开发计划与平台认证清单
```
---
## 工程计划 (`plan/`)
| 文档 | 内容简述 |
| :--- | :--- |
| [development_plan.md](plan/development_plan.md) | S0–S6 骨架垂直切片、风险登记、验收与出口检查、跨切片约束、参考文档地图(与 `technical/` 架构对齐) |
| [certification_checklist.md](plan/certification_checklist.md) | Steam / Switch / 无障碍 / 发布前检查项,并映射到切片 |
---
## 游戏设计 (`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 弹共用敌人位置快照 |
| 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 ARow 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 分配) |
| 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 类成员 PackedByteArrayhas_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_rateDamageContextPool.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=256O(1) 写入,消除 pop_front() O(N) 移位 |
| P6-N39 | consecutive_hits_stacking.md / advanced_mechanics §3.2 | StatusID.VULNERABILITY=8StatusID.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_windowrecord_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.gdclass_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" tagtag 漏写时静默失效);"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` 循环在追加节点前检查此字典并跳过,防止分支节点在 SubPayloadLOGIC_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 SoAZONE_STRIDE=8MAX_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 |