Minecraft 设计思想
卷四 · 重写架构提案

世界表示

稀疏体素、组件化状态、世界作为对象、确定性回放、旧档第一等公民——重写的世界表示要同时服务「一个住几万人的大世界」与「几万个各自的小世界」。

本章命题:重写的世界表示必须同时服务两类产品——生存(一个巨大的、持久的、被频繁改造的世界)与小游戏(成千上万个短命的、模板化的、高并发的世界);原版「一个存档等于一个世界等于一个进程」的等式是两者必须共享一套表示的根源,也是首先值得重写的关系。若某种表示能在原版架构下同时优雅服务两类场景,本章的「重写必要性」论证就是错的。

1问题

第 1 章的四产品矩阵里,「生存世界」与「小游戏世界」列在同一个「原版体验」产品下,享受同一套表示。但它们的需求几乎处处相反:生存世界要大(千万格的尺度)、要持久(十年不关档)、要承载高频改造(每个玩家都在挖);小游戏世界要小(一张竞技场地图)、要短命(一局十几分钟)、要高并发(同时几百局在进行)。原版的表示法——世界即存档、存档绑进程——服务前者是本色出演,服务后者靠的是「多世界」补丁:一个进程里塞几个世界、地图轮换靠整档重启。

本章是架构提案的第一题:如果重写,世界应该怎么表示,才能让两类场景都不再将就?本章的纪律沿用全卷:**给原则与权衡,不给语言与伪代码。**验收物是一张「原版 / 提案」对照表。

先把「将就」的具体形状摆出来,看看今天的小游戏服主在用什么姿势过日子。一局起床战争的生命周期是:从模板目录复制一份地图存档→作为「新世界」挂进服务器→开打→十几分钟后整份存档删掉重挂。这个循环里,世界不是对象,是一份要手动搬运的文件夹;「同时开几百局」意味着几百个文件夹、几百份世界配置、一个负责搬运与清理的调度插件——而调度插件本身是服主用第 12 章那套插件接口手搓的、官方从未设想的基础设施。每一次「重置世界」都是进程级的重操作:卸载世界、释放区块、重建配置。把这些细节看清楚,才能理解下面的提案到底在解决什么。

2稀疏与调色板:把「空」变成免费

原版的表示法在卷三第 1 章拆过:区块、节(16³)、调色板、位打包。它在「稠密世界」(地下满、地表满)的工况下效率很高。但它的稀疏性只做到节级——一个几乎全空的节(高空、深海)仍占一份「空表」的底钱。

提案方向是把稀疏推到底:**默认无物,非空才记账。**一个全空的世界接近零成本;一块有内容的区域按「体素块 + 调色板」记账;更激进的形式(空间树、内容寻址的体素图)可以让「一片雪原」与「一座数千万格的大陆」共享同一套表示——规模从「加载策略问题」变成「数据量问题」。这个方向同时伺候两类产品:大世界按需生长(生存),小世界近乎免费地大量存在(小游戏)。原版的调色板思想是对的,重写的提案不过是把它从「节的优化」升格为「世界的本体论」:世界不是一大块存着的地,是无数小块有内容的记录。

稀疏化还有一层进阶收益:内容寻址带来的去重与分发。如果每块体素数据以内容 hash 定位(同样的内容全局只存一份),那么「两座一模一样的建筑」在存储上就是一次引用;小游戏地图的模板分发从「复制整份文件夹」变成「引用若干块」;玩家之间分享结构,分享的是指针不是行李。这个模型对作弊与隐私的影响同样要先想清楚(内容可被外部引用意味着可被外部枚举)——提案照例只给权衡,不给定论。但对本章的核心问题(海量小世界),内容寻址几乎是必选项:它把「几百局并行」的存储成本从几百份地图压到「一份模板加几百份差异」。

3状态与组件:把行为也搬上数据面

第 13 章讲过物品的组件化(1.20.5:具名组件取代自由标签)。提案方向是把同一场革命推到方块与世界:方块 = 原型 + 组件集合——「箱子」是原型,「朝向」是组件,「内容」是组件,「自定义交互」也应当可以是组件。

收益在数据面(卷二第 8 章的插槽论)的实现端:新行为若能表达为组件,内容创作者就能在插槽里完成今天必须写 Java 的事——第 13 章判定的「必须写代码」四信号里,「新状态」与「新交互」两信号可以消掉大半。代价也要如实列:行为的确定性设计更难(组件的组合需要引擎级的执行模型,而不是各写各的回调);性能预算更难预估(组件组合的爆炸需要调度纪律,第 5 章的契约要重新审计)。提案的态度是「值得试且已有先例」——物品组件化证明了「具名清单优于自由字段」在玩家的真实数据上是成立的,方块没有理由是例外。

举一个具体例子感受差别。假设创作者想要「会发光的纵向半砖」。原版流程:这是一个新方块——要写 Java 注册方块类型、写它的发光属性、注册模型——数据面帮不上忙。组件模型流程:没有「发光纵向半砖」这个类型,只有「砖块原型 + 发光组件 + 半高组件 + 纵向摆放组件」的一次配置——创作者在数据面完成,引擎甚至不需要知道这颗方块的存在。差别不是省力,是权力的位置:前者里引擎是守门人(每个新组合都要它批准),后者里引擎是语法书(任何符合语法的组合自动合法)。卷二第 15 章「动词优于菜单」的原则在数据层的投影:组件模型给创作者的不是更多菜单,是把「定义权」从引擎搬到了插槽。

4世界作为对象:从「进程的宿命」到「数据的操作」

这是本章最核心的提案,也是对原版等式的直接修改:**把「世界」从与进程绑定的庞然大物,降级为可创建、可销毁、可快照、可克隆的数据对象。**开一张小游戏地图,应当像开一个文件;结束一局,应当像关一个文件;几百局并行,应当是几百个对象并存,而不是几百次重启。

这个降级的前置条件是前两节:稀疏表示让「一个世界」小到可以轻易多开;组件化让「一个世界」的状态足够自描述。后置的收益远不止小游戏:世界快照让「备份与回滚」(第 16 章服主的后悔药)从运维手艺变成基本操作;世界克隆让「测试一张地图」「给每个新玩家发一个初始世界」成为自然语法;多世界并存的边界问题(跨世界交互)则应当被设计成显式接口而非意外的 bug 温床——卷三第 3 章讲过的「预约制」教训,在新表示里有机会从补丁升格为协议。

5确定性回放:把「过去」也变成可计算的

卷三第 3 章立过种子的确定性合同:同种子同版本同世界。提案方向把这份合同向前推一步:如果模拟本身也是确定性的(同状态 + 同输入序列 = 同结果),那么「世界的历史」就可以用「初始快照 + 输入日志」完整记录与重放。

这项性质在原版里不存在(模拟包含大量非确定源:随机数、并发的时序、玩家的自由),而它的价值清单长得惊人:反作弊仲裁可以从「猜谁在撒谎」变成「回放看谁不合规」;grief 事件的溯源从「问目击者」变成「调录像」;服务器可以从「只能往前走」变成拥有有限的时间旅行(第 16 章监控仪表盘的终极形态)。达成它的工程代价同样要诚实:模拟的每一步都要消灭非确定源(随机数全部种子化、并发全部有序化),这是对第 5 章因果契约的全面加严——性能与自由度都要付账。提案因此是有条件的:至少对「被标记的 contested 区域」实现回放,全局确定性可以是长期目标而非上线条件。

把它落成一个服主都懂的场面。某个清晨,玩家申诉:夜间仓库被洗劫。今天的处理流程是查日志(谁进过区域)、问目击者、凭经验判断——结论常是「证据不足」。回放能力下的处理流程是:调出该区域当时的输入日志,从快照重放那五分钟,谁拆了第一块墙、东西被谁捡走、此人的移动是否物理上可能——一目了然,且这份重放本身就是呈堂证据(同样的重放任何人执行结果一致)。仲裁从「信任的较量」变成「计算的核对」——对第 12 章那套服务器立法来说,这等于给地方法院配上了监控录像:管辖权之争不变,取证的成本塌缩了。

6旧档:第一等公民不是口号

本章的最后一条提案与第 9 章(兼容性)互相指认:**旧档的读取支持,必须在新表示的立项清单里列为 1.0 功能,而不是 2.0 计划。**实现层这意味着:旧格式(Region/Anvil、NBT、物品组件)的读取器永久内置,与新表示并行存在——新引擎从出生那天起就能打开十五年的世界。

第 2 章讲过这笔伦理账(世界比引擎活得久),本章补上架构账:旧档读取不是「迁移工具」,是新表示自身的导入接口——把它当外挂工具的项目,会在第一个十年遇到「老玩家进不来」的冷启动死结;把它当核心功能的项目,等于自带了 Minecraft 十五年积累的全部世界资产作为首发内容库。卷一第 13 章说过「开源能复制规则,不能复制二十年存档」——本章的提案就是那个「不能」的技术解:存档复制不走开源,走兼容。

7对照表

维度原版提案方向服务的场景
稀疏性节级调色板,节内稠密默认无物,非空记账,规模即数据量大世界生长 / 小世界海量
状态模型方块状态 + 方块实体方块 = 原型 + 具名组件数据面扩容,创作者免写 Java
世界与进程一存档 = 一世界 = 一进程世界为对象:创建/销毁/快照/克隆小游戏并发、备份回滚、测试
历史可计算确定性仅限生成(种子)事件溯源:快照 + 输入日志可重放反作弊仲裁、grief 溯源
旧档官方转换器(DFU)旧格式读取器内置为 1.0 功能十五年世界资产自带入场

表底声明沿用全卷纪律:以上是原则方向与权衡,不是实施方案;每一行都可以有多个合法实现,选择留给答题人。

8这一章留下什么

  • 爱好者:这张表翻译成人话:重写的世界里,「开一局新地图」会和「开一张新床」一样便宜,而你的老存档是新世界的第一等公民。
  • 开发者:能抄走的是五条方向的权衡表,以及贯穿它们的同一个判据——表示法要同时伺候最大的世界和最多的世界;任何只优化一端的方案,都在把另一端的产品形态拱手让人。抄不走的是做出这些取舍所需的生态理解:知道「旧档是入场券」容易,知道「十五年的存档里什么必须件件保真」需要读完整整三卷。

On this page