# 构建核心 (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` 时变黄/变红。
