- 审计报告 D 节 + G.2 表标注 D1/D2/D5 已修复(待运行时验收),修正 D1 修复方案为「单选枚举 + ==」(复核发现设计器 core_tab 以下标写入 feature_tags, 不宜改 2 的幂位标志) - architecture_design ADR-R5-N2 与 core_wand §1 的现状 callout 同步更新为已修复 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
14 KiB
构建核心 (Core) 设计文档
0. 概念定位
Core(构建核心) 是玩家持有的"武器主体"。它定义了:
- 插槽拓扑 (Slot Topology):有多少个插槽、如何排列(线性/矩阵/电路板)。
- 基础属性 (Base Stats):法杖自带的充能速度、容量、基础蓝耗。
- 特殊规则 (Core Rules):部分高阶 Core 拥有改变执行规则的特性(如持久内存、双流并行)。
Core 本身不执行法术,它是 SpellDeck 的"容器",决定了 SpellEvaluator 的执行环境。
1. 数据结构 (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。
{
"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 等)均为目标设计。详见 审计报告。
实现约束说明(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)个槽(02),后向流取后6),中间槽(index = slot_count / 2,此处为 index 3)由前向流额外执行(不计入后向流)。 UI 上奇数情况下中间槽显示"▶ 仅前流"标注,防止玩家困惑。 示例(7 槽):floor(slot_count / 2)个槽(4[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。
{
"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 获取:
# 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时变黄/变红。