四档扩展模型
框架把扩展单元分为四档:数据包、资源包、插件、Mod。四档不是四个目录,而是四种权力边界:能不能执行代码、能不能改变既有节点、卸载时要不要撤销跨节点改动、需要多强的信任。
一个单元只能落在一档。档位不是作者自报的标签,而是由包内容与清单声明共同计算的结论。
三个问题
| 顺序 | 问题 | 判定 |
|---|---|---|
| 第一问 | 是否含可执行代码? | 否 → 进入第三问;是 → 进入第二问 |
| 第二问 | 是否产生写边? | 否 → 插件;是 → Mod |
| 第三问 | 作用面是内容还是表现? | 内容 → 数据包;表现 → 资源包 |
写边指改变了自身子树之外既有节点的可观测行为。订阅通知、inject 服务、往实现位新增一个条目,都不改变既有节点的行为,因此是读边或纯新增,不构成写边。
无代码单元在第二问之前已经分流。 数据包与资源包没有可执行代码,也就不存在「改变逻辑行为」的能力。它们可以覆盖既有条目或资产路径,但那是覆盖语义,不是写边。
四档对照
| 维度 | 数据包 | 资源包 | 插件 | Mod |
|---|---|---|---|---|
| 是否含可执行代码 | 否 | 否 | 是 | 是 |
| 作用面 | registry 条目 | 资产路径 | 新增能力节点 | 既有能力节点 |
| 写边 | 不存在 | 不存在 | 无 | 至少一条 |
| 典型落点 | data/ 条目文件 | assets/ 虚拟路径 | 实现位登记 | 机制插槽、intercept、isolate 域内 provide |
| 覆盖语义 | 字段级 replace / merge / append | 路径级整体替换,禁止合并 | 只新增 | 叠加层改写 |
| 卸载义务 | 撤销自己写过的条目 | 摘下自己压入的层 | 无跨节点义务 | 精确撤销跨节点改动 |
| 信任等级 | 可放任,配合 schema 与预算 | 可放任,另加资源约束 | 同 realm 受审代码 | 同 realm 受审代码,且必须审阅 |
「卸载义务」不是礼貌约定,而是装载器实际执行的撤销:数据包的每一层、资源包的每一个来源层、第三方实现的每一个条目、机制插槽的每一个 hook,都挂在对应单元的 effect 上,单元卸载时自动摘除。内置条目由所属大插件登记,不随扩展卸载。
删除测试
判断「内容」还是「表现」,不看文件扩展名,也不看作者觉得它重要不重要,而看整体移除后的结果。
| 移除后的观察 | 结论 | 档位 |
|---|---|---|
| 逻辑流程仍完整可运行,只是看不到、听不到或读不懂 | 表现 | 资源包 |
| 流程无法推进,或既有内容无法被正确解释 | 内容 | 数据包 |
「解释」与「消费」是这条判据的核心区别:
| 框架行为 | 含义 | 例子 |
|---|---|---|
| 按语义消费 | 索引、引用、求值、执行 | 章节节点、条件、变量、指令 |
| 只做渲染、取文本、查表 | 不参与逻辑判定 | 立绘、背景、BGM、字体、lang 文本表 |
一组边界例子
| 单元内容 | 删除后的结果 | 档位 |
|---|---|---|
| 一段新增章节的节点与对话 | 该章节不存在,主线流程断在这里 | 数据包 |
| 一套替换用立绘 | 流程照常推进,只是角色显示回退 | 资源包 |
| 一张界面文本表 | 流程照常推进,只是文字回退 | 资源包 |
| 一个新的过场渲染器 | 流程照常推进,只是该效果不可用 | 插件(注册实现位) |
| 改写伤害公式的 hook | 数值结果改变,既有战斗逻辑被改写 | Mod |
地图必须拆成两层
地图是最容易被混在一起的内容。框架要求把地图拆成表现与逻辑两层,分别归入不同档位。
| 层 | 包含什么 | 档位 |
|---|---|---|
| 地图表现 | 背景图、地块贴图、地图 UI 皮肤、图标 | 资源包 |
| 地图逻辑 | 节点、连接关系、区域与坐标数据 | 数据包 |
强制约束:地图定义不得携带执行语义。地图数据只描述「有什么、在哪里、连到哪」,一切触发时机与条件由剧本侧声明。这样同一张地图可以被不同章节复用,也不会让地图包变成一段不可审计的逻辑。
章节包
章节包是只含无代码单元的分发包,用来追加章节内容。它内部可以有数据包与资源包,但不能有插件或 Mod。因此章节包与插件、Mod 在原理上不可能重叠。
| 关于章节包的判断 | 结论 |
|---|---|
| 能否包含插件或 Mod? | 不能,只能含无代码单元 |
| 一个章节包算一个单元吗? | 不算,分发包内各单元分别判定 |
| 能否把多个章节打成一个包? | 可以,用分发包组织,每个单元仍有自己的 pack.json |
| 能否借章节包绕过写边检查? | 不能,包内每个单元按自己的内容判定 |
分发包本身只是装载单位,不改变其中任何单元的档位。具体布局见分发包与包结构。
判定在实现中的落点
装载器不读取作者的口头说明,只看两类事实:
- 包内容:是否出现
code/、data/、assets/。 - 清单声明:
writes.hooks、writes.intercepts、writes.isolateProvides是否为空,以及registers、caps是否出现。
由此得到几条硬规则:
| 情况 | 结果 |
|---|---|
| 代码单元的写边声明非空 | 计算为 mod |
| 代码单元的写边声明为空 | 计算为 plugin |
计算档位与清单 kind 不一致 | 拒绝,GUARD_CLASSIFICATION_MISMATCH |
数据包出现 code/、登记项、写边或能力请求 | 拒绝,GUARD_CLASSIFICATION_MISMATCH |
数据包出现 assets/,或资源包出现 data/ | 拒绝,GUARD_CLASSIFICATION_MISMATCH |
代码单元出现 data/ 或 assets/ | 拒绝,GUARD_CLASSIFICATION_MISMATCH |
| 资源包或数据包试图影响解码路径 | 拒绝,GUARD_DECODE_PATH_FORBIDDEN |
原因码的完整说明见装载原因码,判定的执行顺序见装载管线与 guard。
常见误解
常见误解
- 「清单里写
kind: plugin就是插件。」kind必须与内容事实、写边声明计算出的结果一致;不一致会被拒绝。 - 「数据包覆盖了既有条目,所以它也是 Mod。」 无代码单元不产生写边,覆盖语义与写边是两回事。
- 「资源包可以任意改动界面。」 普通资产覆盖不引入交互行为;已登记的控件皮肤允许通过受校验的
controls.json调整白名单中的位置、尺寸与层级,不能改事件绑定、键盘顺序或安全关键界面。 - 「分发包整体算一个扩展单元。」 分发包内每个单元分别判定、分别装载、分别卸载。
- 「章节包只要叫章节包就能带插件。」 章节包按定义只含无代码单元,带代码就不再是章节包。
与其他概念的关系
- 档位确定后,下一步看它被允许写什么:policy 与写边。
- 想知道每种档位具体挂在哪里:插槽与实现位。
- 想把上面的声明写进真实文件:分发包与包结构。
- 想理解大插件如何固定扩展点:大插件与小插件。