脚本与模组的双层
沙箱层与原生层缺一不可——单层必失败的两种姿势、崩溃域的分离、稳定 API 的三条线、热重载与版本化 schema、作者体验作为产品的正式组成部分。
本章命题:重写的扩展体系必须是双层结构——沙箱层(安全、热重载、受限的内容创作)与原生层(全能力、版本化、风险自担的系统扩展)缺一不可:只留沙箱层会死于能力天花板与需求队列,只留原生层会死于崩溃域合并与信任成本;两层之间必须有「晋升通道」。若单层方案(无论哪层)能同时满足安全、能力与生态规模三要素,本命题就是错的。
1问题
今天的扩展体系是历史长成的两层结构:数据包在伞内(安全、受限、承诺兼容),模组在伞外(全能、脆弱、自谋生路)——第 13 章讲过这条边界的政治学。重写者的诱惑是把这个「历史意外」简化成一个干净的单层方案:「既然有了一个强大的脚本层,为什么还要保留原生模组?」或者反过来:「脚本层管不了大事,干脆全开放原生。」
本章论证:两个方向都会失败,且失败的方式可以精确预言——因为今天的生态已经把两种失败各演示了一半。数据包的天花板是「能力天花板」(第 8 章的四信号:新动词、新界面、新状态、新规模,任何一个都逼作者出走去写 Java);模组的地狱是「崩溃域合并」(第 12 章:一个模组的错误炸掉整个服务器,兼容矩阵三维爆炸)。理想的双层,就是让「安全的留下」与「全能的进来」各有其门,且门之间有路。
2全脚本与全原生的失败模式
先做两个思想实验,把两条绝路走到底。
全脚本方案(取消原生层,一切扩展走沙箱):第一个死因是能力天花板。沙箱的本质是受限——受限的表达必然挂一漏万,社区会永远排在「求官方开放 XX 插槽」的队伍里,而每开一个插槽都是一次接口承诺(第 13 章的债务论)。第二个死因更隐蔽:性能与确定性的税。所有逻辑跑在解释器里,每刻的调度预算(第 5 章)要多养一层虚拟机;红石章那种「严格序」的硬实时需求,沙箱很难以通用方式承诺。第三个死因是安全军备:沙箱与逃逸是永恒的猫鼠游戏,把全部生态押在沙箱的完美上,等于把地基押在一条从未在历史上完全成立过的假设上。
全原生方案(取消沙箱层,一切扩展直接进引擎):第一个死因是崩溃域合并——所有扩展与核心共享命运,一行坏代码炸掉整个世界,普通服主被迫成为代码审查员。第二个死因是信任成本的陡增:第 13 章讲过「任意代码无法审核」,没有沙箱层,平台化的一切可能(商店、审核、签约)都失去前提——Marketplace 的技术地基就是「可审核的格式」(第 15 章)。第三个死因是作者门槛的固化:原生层天然要求工程能力,而生态的绝大多数作者是「会一点脚本」的爱好者——单原生层的生态,是极客寡头的生态。
两个失败互为镜像:全脚本死于「什么都做不了」,全原生死于「做什么都危险」。结论自然浮出:双层不是折中,是分工。
用一台真实的服务器需求把这条分工演一遍。假设业主要开一台起床战争服务器——今天的主流做法是装一套插件(Bukkit 系),因为需求清单里全是第 8 章那四信号的成员:排队与匹配是跨玩家的状态机,自动开局要挂事件,计分要每刻刷新,房间要随开随销。试着用纯数据包写?计分板与函数能拼出一个「能看」的版本,但队列的公平性、掉线补位的原子性、几百局并行的性能,每一项都在把数据包往解释器的极限外推——这就是「能力天花板」从名词变成体验的时刻。反过来假设业主要的是「在原版规则下微调掉落与经济」,插件方案反而笨重:为一个掉落表装一套插件运行时,还要追着版本更新跑——数据包一行配置的事。今天的服主凭经验在两套工具间跳来跳去,双层模型的提案不过是把这条经验曲线画成正式的制度。
3双层模型与崩溃域的分离
把两层的各自权限写成一张表(本章的验收物):
| 沙箱层 | 原生层 | |
|---|---|---|
| 能力 | 插槽内的全部数据创作 + 受限脚本逻辑 | 引擎全能力:新类型、新动词、新界面、渲染 |
| 安全 | 沙箱内执行,资源受限,系统隔离 | 任意代码,信任自担 |
| 崩溃域 | 脚本自身——出错杀掉脚本,世界无恙 | 与核心同域——错误影响一切 |
| 部署 | 热重载,随存档分发 | 随启动安装,随版本更新 |
| 审核性 | 可静态审核(Marketplace 兼容) | 不可自动审核(自由分发区) |
| 适合谁 | 爱好者、地图作者、轻量玩法 | 工程向作者、底层机制、性能敏感系统 |
表中最要紧的一行是崩溃域。今天的模组出错,炸的是整个服务器(第 12 章的兼容地狱);沙箱层的脚本出错,炸的只是脚本自己——被系统停用,世界继续转,其他脚本继续跑。崩溃域的分离不是技术细节,是生态的保险制度:它把「试错的代价」从全体玩家头上,转移到了试错者自己身上——这正是第 2 章「先卖再修」在扩展层的镜像:允许冒险,但让冒险者承担可预期的风险。
崩溃域分离还有一层常被低估的价值:它改变了作者的胆量。今天的服主安装第三方插件前要做的心理建设是「这东西可能搞死我的服」——于是要么不敢装(生态萎缩),要么先开测试服再上线(成本翻倍)。沙箱层的承诺把这道心理门槛拆了:装一个脚本的风险被框定在「它自己不工作」,服主敢装,作者的作品才有观众。第 6 章讲过作者漏斗,崩溃域是漏斗中段的阀门——阀门一关一开之间,是两种生态命运。
两层的语法同构也值得写进设计:如果脚本层的概念(事件、组件、世界谓词)与原生层一一对应——同一套词汇,只是权限不同——那么作者从沙箱晋升原生时的学习成本就只有「工程化」这一项,而不是「重学一门外语」。今天数据包与模组两套生态的词汇几乎是平行宇宙(函数 vs 事件监听,标签 vs 注册表),晋升意味着推倒重学;双层设计里这是可以修掉的断层。
表的另一行同样关键:晋升通道。沙箱层里长成的成熟作品(一个被几万服务器使用的玩法脚本),应当有一条路「转正」为原生实现——就像活塞从模组走进本体(卷一第 4 章)。晋升通道的存在让沙箱层不只是玩具场,而是原生层的苗圃;没有通道的双层,是两个割裂的阶级。
4稳定 API 面:三条线
原生层的「风险自担」不等于「完全放养」——重写应当把今天未承认的契约(第 10 章「未承认的公共 API」)转正为显式承诺的稳定面,分三条线。
世界 API:读写世界状态的稳定谓词与操作——它的语义锚是卷三第 18 章的不变量(体素账本的读写规则)。事件 API:把今天「藏在实现里的通知」(方块更新、实体事件)整理成显式的事件目录——第 4 章讲过今天的事件系统是 Bukkit 从实现缝隙里总结出来的,重写版应当让事件成为设计出来的接口,而不是观测出来的规律。协议 API:第 10 章说过协议是被整个生态当宪法用的未承认契约——转正它,意味着版本变更要遵守自己的规则(公告期、弃用周期),而不是每次内部重构都变成全生态的加班周。
三条线共同的制度是版本化 schema(第 7 章组件化的推广):每个接口带版本号,变更有公告期,弃用不删除(先并行、后退役)。今天模组作者的「版本末日」(第 12 章)来自「官方无承诺、社区全靠追」;版本化 schema 把这场每年一度的末日,变成可预期的迁移日历。
还有一个归属问题要正面回答:**稳定面由谁维护?**答案是三层共治:官方维护语义(不变量与核心行为的最终解释权,否则承诺无从谈起);接口委员会维护节奏(版本与弃用周期——委员会里应有模组、服务端、数据包三方的席位,因为每一条接口变更的成本由三方分摊);社区维护覆盖面(边缘用法的发现、测试集的扩充——第 16 章的「语义参考实现」角色在这里制度化为「行为测试集即规格的一部分」)。第 8 章的四步过程(观察→依赖→冻结)是自发形成的旧路;稳定面是把这条路修成有路灯的大道——方向相同,告别摸黑。
5热重载与 schema:作者的生产力就是生态的生产力
沙箱层的 /reload 式热重载(第 13 章)是它最大的生产力优势——几秒一次的调试循环。双层方案把这个优势制度化并向上渗透:沙箱层默认热重载;原生层提供受控的有限重载(至少配置与资源层可热更),并把「改一行要重启整个游戏」定位为待消灭的痛点而非行业常态。
配套的制度是 schema 的生命周期管理:每个 API 的变更走「提案 → 公告 → 双轨并行 → 弃用 → 移除」的标准周期,双轨期给生态留出跟进时间。第 2 章讲过 DataFixerUpper 那套「只增不减」的迁移体系——那是数据的版本化;本章提案是它的接口版:数据的旧格式读得开,接口的旧版本留得活。两套制度合起来,才是对「世界比引擎活得久」的完整兑现——世界活着,围着世界写的代码也该有活路。
6作者体验是产品
本章最后一个提案最软也最硬:把作者体验当成产品的正式组成部分。错误信息要能定位问题(而不是一串混淆类名);本地测试环境要开箱即用(而不是先搭一晚上工具链);文档要与版本同步(而不是 Wiki 上的考古现场)。第 8 章说过红石的计算机教程是「把天才的成果转成普通人可以攀爬的坡道」——坡道的坡度,就是作者体验。
这条提案的经济学依据是生态的漏斗模型:作者从沙箱层入门(门槛低、反馈快),成熟后晋升原生层(能力大、责任重),每一层的流失都由「下一层的供给」买单——沙箱层的报错质量,决定原生层五年后的作者存量。今天的数据包与模组生态已经在验证这个漏斗(无数知名模组作者的第一行代码是数据包函数),重写方案若把作者体验当「锦上添花」,漏斗的最上层就会干涸——而漏斗干涸的生态,等不到下一个活塞的发明人。
7这一章留下什么
- 爱好者:那张双层权限表翻译成人话:想试试写玩法的你,从杀不死世界的那一层入门;写成了大事,再搬进责任更大的那层——楼梯已经画在这章里了。
- 开发者:能抄走的是双层权限表与崩溃域分离的原则——安全与能力各给一层,晋升通道连通两层,稳定 API 走版本化 schema;以及「作者体验是留存率」这个最容易被砍预算的常识。抄不走的是漏斗顶端的供水:沙箱层需要一门足够简单又足够诚实的脚本语言与文档体系——那是又一轮十年攒出来的社区资产。