# 构建核心 (Core) 设计文档 ## 0. 概念定位 **Core(构建核心)** 是玩家持有的"武器主体"。它定义了: 1. **插槽拓扑 (Slot Topology)**:有多少个插槽、如何排列(线性/矩阵/电路板)。 2. **基础属性 (Base Stats)**:法杖自带的充能速度、容量、基础蓝耗。 3. **特殊规则 (Core Rules)**:部分高阶 Core 拥有改变执行规则的特性(如持久内存、双流并行)。 > Core 本身不执行法术,它是 SpellDeck 的"容器",决定了 SpellEvaluator 的执行环境。 --- ## 1. 数据结构 (GDScript) ```gdscript # core_definition.gd # 存储于 res://resources/cores/ 目录下的 .tres Resource 文件 class_name CoreDefinition extends Resource ## 核心唯一 ID(如 "wand_basic", "staff_matrix") @export var id: String ## 显示名称 @export var name: String ## 稀有度 (0=普通, 1=稀有, 2=史诗, 3=传说) @export var rarity: int = 0 ## 插槽拓扑类型 @export_enum("LINEAR", "MATRIX_2X4", "CIRCUIT") var topology: int = 0 ## 插槽总数量 @export var slot_count: int = 5 ## 矩阵布局尺寸(仅 MATRIX / CIRCUIT 类型有效) @export var grid_cols: int = 4 @export var grid_rows: int = 2 ## 特殊插槽配置(每个插槽的约束信息) ## Array[Dictionary]: { "index": int, "allowed_types": Array[int], "tags": Array[String] } @export var slot_configs: Array = [] ## CIRCUIT 拓扑的有向边列表(LINEAR/MATRIX 留空即可) ## Array[Dictionary]: { "from": int, "to": int } ## _flatten_circuit() 从 core.edges 读取边表;禁止在 slot_configs 内嵌 "edges" key。 @export var edges: Array = [] ## 法杖基础属性 @export var base_mana_capacity: float = 50.0 # 法杖蓝上限 @export var base_mana_regen: float = 5.0 # 每秒回蓝 @export var base_cast_delay: float = 0.25 # 基础施法后摇(乘算基准) @export var base_recharge_time: float = 0.5 # 完整一轮打完后的充能时间 ## Core 特性标签,驱动特殊执行规则 ## ⚠️ 实现现状(2026-07-20):代码实为 `@export var feature_tags: int = 0`(int 位掩码,非 Array[String]); ## CoreFeatureTag 常量为 int 1..5(单选枚举,设计器 OptionButton 下标写入); ## 判定已由 `&` 修复为 `==`(dc05eea,原 `&` 因非 2 的幂会串扰;见审计 D 节)。 @export var feature_tags: Array[String] = [] ## 图标路径 @export var icon: Texture2D ``` --- ## 2. 插槽拓扑类型 ### 2.1 LINEAR(线性) ``` [ 0 ] -> [ 1 ] -> [ 2 ] -> [ 3 ] -> [ 4 ] ``` - 最基础的类型,执行流从左到右。 - 新手友好,等同于标准 Noita 法杖。 - **代表 Core**: `wand_basic`(5槽)、`staff_long`(10槽) ### 2.2 MATRIX(矩阵) ``` 行 A: [ 0 ] [ 1 ] [ 2 ] [ 3 ] 行 B: [ 4 ] [ 5 ] [ 6 ] [ 7 ] ``` - 执行流默认沿行 A 执行,但行 B 中的法术在 **邻接行 A 中对应位置的法术** 执行时同时触发(**邻接加成**)。 - **邻接规则**: 将 `slot[i]` 与 `slot[i + grid_cols]` 视为"竖向相邻",两者同类时触发 `adjacency_bonus`。 - **代表 Core**: `matrix_board_2x4`(8槽) **邻接加成效果表(P6-N13 权威定义)**: 预编译阶段 `_flatten_matrix` 检测以下组合,注入对应隐式 MODIFIER 节点: | 行 A 法术类型 | 行 B 法术类型 | 触发条件 | 注入效果 | 说明 | | :--- | :--- | :--- | :--- | :--- | | **ACTION** | **ACTION**(同 ID) | 完全相同的 ACTION 对齐 | 伤害 ×1.5(`damage_mult +0.5`) | "强化同类弹" | | **ACTION** | **MODIFIER** | 任意 | 该 MODIFIER 效果额外再施加一次(等效双倍加成) | "增幅器加倍" | | **MODIFIER** | **MODIFIER**(同 ID) | 完全相同的 MODIFIER 对齐 | 修正值 ×2(平直叠加而非乘算) | "共鸣修正" | | **ACTION** | **TRIGGER** | 任意 | 该 TRIGGER 的 `proc_rate ×1.5`(最大 1.0) | "增压触发" ⚠️**未实现**(代码只实现 ACTION+ACTION / ACTION+MODIFIER / MODIFIER+MODIFIER 三行)| | **LOGIC** | **任意** | 任意 | 无加成(LOGIC 节点不参与邻接计算) | 设计原则:逻辑层不受物理拓扑影响 | | **空槽** | **任意** | 行 A 为空 | 无加成 | 空槽不传递邻接效果 | > **实现说明**:`_flatten_matrix` 在预编译时查表,按上表注入 `ImplicitModifierNode`;运行时 `SpellEvaluator` 不感知矩阵结构,仅执行线性化后的节点序列。邻接加成不累叠(同一个 ACTION 最多受益于一次邻接加成)。 ### 2.3 CIRCUIT(电路板) ``` [ 2 ] ↑ [ 0 ] -> [ 1 ] -> [ 3 ] -> [ 4 ] ↓ [ 5 ] -> [ 6 ] ``` - 插槽有明确的 **有向边(Edge)** 连接,执行流可分叉。 - 玩家可在 `slot_configs` 中配置分叉点(Splitter)和合并点(Merger)。 - **代表 Core**: `circuit_fork`(7槽,含1个分叉点) **CIRCUIT Core 的 `CoreDefinition` 资源格式**: `edges` 列表通过 `CoreDefinition.edges`(`@export var edges: Array = []`)存储, `slot_configs` 仅存储每个槽的类型约束与标签;不再在 slot_configs 内部嵌套 `"edges"` key。 ```json { "topology": "CIRCUIT", "slot_configs": [ { "index": 0, "allowed_types": ["ACTION","MODIFIER","TRIGGER","LOGIC"], "tags": [] }, { "index": 1, "allowed_types": ["ACTION","MODIFIER","TRIGGER","LOGIC"], "tags": ["splitter"] }, { "index": 2, "allowed_types": ["ACTION","MODIFIER"], "tags": ["branch_a"] }, { "index": 3, "allowed_types": ["ACTION","MODIFIER","TRIGGER","LOGIC"], "tags": [] }, { "index": 4, "allowed_types": ["ACTION","MODIFIER","TRIGGER","LOGIC"], "tags": [] }, { "index": 5, "allowed_types": ["ACTION","MODIFIER"], "tags": ["branch_b"] }, { "index": 6, "allowed_types": ["ACTION","MODIFIER"], "tags": ["branch_b"] } ], "edges": [ { "from": 0, "to": 1 }, { "from": 1, "to": 2 }, { "from": 1, "to": 3 }, { "from": 1, "to": 5 }, { "from": 3, "to": 4 }, { "from": 5, "to": 6 } ] } ``` > `CoreDefinition.edges` 是 `_flatten_circuit()` 做 Kahn 算法拓扑排序的唯一边表来源。 > `edges` 为空则视为 LINEAR 拓扑(降级处理)。LINEAR/MATRIX Core 的 `edges` 保持空数组即可。 --- ## 3. 特性标签 (Feature Tags) | 标签 | 效果 | 稀有度要求 | | :--- | :--- | :--- | | `persistent_memory` | 法杖的寄存器 (R1-R4) 在两次施法之间保留数值(跨帧持久化) | 稀有+ | | `dual_stream` | 同时从插槽序列头尾各执行一次,两条流的弹头同时发射 | 史诗 | | `shuffle_deck` | 每次充能完成后,随机打乱插槽执行顺序 | 稀有 | | `infinite_spells` | 法力耗尽时不停止执行,但每发增加 1 点 HP 代价(需配合 `heavy_cost` 法术) | 传说 | | `always_cast_last` | 无论 Deck 如何执行,最后一个插槽的法术在一轮结束时**必定**执行一次 | 稀有 | > **⚠️ 实现现状 (2026-07-20 审计)**:5 个特性标签中**只有 `persistent_memory` 实现**;`dual_stream` / `shuffle_deck` / `infinite_spells` / `always_cast_last` 在代码里除常量定义外**无任何逻辑引用**(`core_feature_tag.gd:6` 自注"S6 P1 实现")。下文对这 4 个标签的详细行为规范(P5-N2/P6-N7/P6-N12 等)均为目标设计。详见 [审计报告](../../docs_dev/doc_code_audit_2026-07-20.md)。 > **实现约束说明(P5-N2)**: > - **`shuffle_deck`**:仅随机化**独立 ACTION 节点**的执行顺序;MODIFIER 节点始终与其紧接的 ACTION 视为原子单元整体移动,防止修正器与错误的动作配对,保持构建意图的确定性。UI 上以"卡组"为单位展示被打乱的顺序,而非单张卡牌。 > - **`dual_stream`**:两条执行流**各自独立计算 ops 消耗**,每条流的 MAX_OPS 上限 = `cpu_limit × MAX_OPS_PER_CPU / 2`(平均分配)。双流模式下单帧总指令数与单流相同,不额外增加运算力消耗;实际可用插槽从序列头尾各取一半,两条流不可互相访问对方的槽位。 > **奇数插槽行为(P6-N12 规范)**:当 `slot_count` 为奇数时(如 7 槽),前向流取前 `floor(slot_count / 2)` 个槽(0~2),后向流取后 `floor(slot_count / 2)` 个槽(4~6),**中间槽(index = slot_count / 2,此处为 index 3)由前向流额外执行**(不计入后向流)。 > UI 上奇数情况下中间槽显示"▶ 仅前流"标注,防止玩家困惑。 > 示例(7 槽):`[0前][1前][2前] | [3前专属] | [6后][5后][4后]`。 > - **`always_cast_last`**:无论 Deck 如何执行,一轮结束时对最后一个插槽的法术额外执行一次。 > **边界行为(P6-N7 补充)**: > - 若最后一个插槽为 **ACTION**:正常发射一颗子弹(使用一轮结束时的当前 `CastStats`,所有 MODIFIER 已在上一轮累积)。 > - 若最后一个插槽为 **MODIFIER**:静默跳过(MODIFIER 必须有后续 ACTION 才能生效,单独执行没有效果),此次额外执行不消耗 mana。 > - 若最后一个插槽为 **TRIGGER** 或 **LOGIC**:同样静默跳过(无 ACTION 上下文,执行无意义)。 > - UI 提示:最后一个插槽为非 ACTION 时,卡牌展示灰色小锁图标(∅无效提示),防止玩家误以为 MODIFIER 也会被额外执行而混淡。 > **Feature Tag 引用规范(ADR-R5-N2)**:业务代码中必须通过 `CoreFeatureTag.PERSISTENT_MEMORY`、`CoreFeatureTag.DUAL_STREAM` 等常量引用标签,禁止使用裸字符串。详见 `architecture_design.md §ADR-R5-N2`。 --- ## 4. Core 实例(参考设计) | ID | 名称 | 稀有度 | 拓扑 | 插槽数 | 特性 | 定位 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | `wand_basic` | 朴素法杖 | 普通 | LINEAR | 5 | — | 初始装备,入门用 | | `wand_fast` | 急促法杖 | 普通 | LINEAR | 4 | — | 基础施法延迟 ×0.7,容量小 | | `staff_long` | 长卷轴 | 稀有 | LINEAR | 10 | — | 插槽多,回蓝慢 | | `matrix_board` | 构造板 | 稀有 | MATRIX_2X4 | 8 | — | 解锁邻接加成玩法 | | `wand_memory` | 记忆法杖 | 史诗 | LINEAR | 6 | `persistent_memory` | 寄存器跨帧,启用计数器构建 | | `circuit_fork` | 分叉回路 | 史诗 | CIRCUIT | 7 | — | 双分支执行,高复杂度 | | `wand_eternal` | 永恒法杖 | 传说 | LINEAR | 8 | `infinite_spells` | 卖血流终极武器 | --- ## 5. 序列化格式 (SaveData) 法杖的存档需要记录:Core 类型 + 每个插槽内填的法术 ID。 ```json { "schema_version": 1, "wands": [ { "core_id": "wand_basic", "slots": ["double_cast", "spark_bolt", "damage_plus", null, null] }, { "core_id": "matrix_board", "slots": ["trigger_hit", "chain_bolt", null, null, "homing", "damage_plus", null, null] } ] } ``` **注意**: - `null` 表示该插槽为空。 - 加载时如果 `core_id` 或法术 ID 不存在于注册表,记录警告日志并跳过,不崩溃(向前兼容)。 - `schema_version` 字段用于未来迁移,初始值为 1。 --- ## 6. 与 SpellEvaluator 的接口 `SpellEvaluator` 在执行前需要从 `Core` 获取: ```gdscript # spell_evaluator.gd 伪代码 # SpellEvaluator 采用两阶段架构: # - compile_wand():法杖装备/换牌时调用,结果缓存为 CompiledDeck # - execute_compiled():每次施法时执行缓存的线性化节点序列 # ── 阶段 1:编译(法杖装备/换牌时调用,结果缓存于 CompiledDeck)────────────────── # 参数类型:raw_deck: SpellDeck(通过 deck.nodes[i] 访问节点序列,禁止传入 Array[SpellNode]) # ⚠️ 实现现状(2026-07-20):真实签名 compile_wand(core, raw_nodes: Array)——第 2 参是 Array 而非 SpellDeck func compile_wand(core: CoreDefinition, raw_deck: SpellDeck) -> CompiledDeck: match core.topology: CoreDefinition.LINEAR: return CompiledDeck.new(raw_deck.nodes) CoreDefinition.MATRIX_2X4: return _flatten_matrix(raw_deck, core) # 注入邻接 MODIFIER CoreDefinition.CIRCUIT: # 传 core.edges(顶层有向边表),core.slot_configs 仅存槽约束/标签,不含拓扑边 return _flatten_circuit(raw_deck, core) # Kahn 拓扑排序 + LOGIC_FORK 注入 return CompiledDeck.new([]) # 未知拓扑降级为空 Deck # ── 阶段 2:执行(每次施法时调用缓存的 CompiledDeck)──────────────────────────── # 必须接受 core: CoreDefinition 参数,用于读取 core.feature_tags 判断寄存器持久策略。 # 禁止将 feature_tags 复制到 SpellContext(SpellContext 不持有 Core 引用;ctx.core_feature_tags 不存在)。 # ⚠️ 实现现状(2026-07-20):真实签名 execute_compiled(compiled, caster_id: int, spawn_pos: Vector2)——不传 ctx/core,feature_tags 从 CompiledDeck 编译期快照读取 func execute_compiled(compiled: CompiledDeck, ctx: SpellContext, core: CoreDefinition) -> void: _run_deck(compiled, ctx) # SpellEvaluator 不感知拓扑,仅执行线性化节点序列 # 执行完毕后处理特性标签(直接访问传入的 CoreDefinition) if CoreFeatureTag.PERSISTENT_MEMORY not in core.feature_tags: # ✅ 使用常量(ADR-R5-N2) ctx.registers.fill(0.0) # 非持久内存,帧结束后清零(registers 即 "memory_bank",见 architecture_design.md §3.2) ``` --- ## 7. UI 集成要点 - **背包界面**:根据 `topology` 渲染不同的插槽布局组件(LINEAR 显示横排,MATRIX 显示网格)。 - **拓扑提示**:悬停插槽时显示该插槽的 `allowed_types` 约束(如"此槽仅接受 Trigger 类型")。 - **测试靶场**:在商店界面提供靶人,玩家可立即测试当前法杖效果,**无需进入战斗**。 - **`cpu_limit` 可视化**:在法杖编辑 UI 显示当前构建消耗的"运算力"进度条,接近 `cpu_limit` 时变黄/变红。