可以抛弃的历史偶然
Java 运行时、全局 20 TPS、双版本分裂、部分实现细节的语义冻结——每一条「偶然」都能抛,但每一条都要先证明不变量有新的安身之处。
本章命题:以下四样东西是历史偶然而非设计本质——语言运行时、全局单一时钟、双版本分裂、以及部分实现细节的语义冻结——它们都可以抛弃,但每一条都必须先回答同一个问题:它原先托管的那个不变量,搬家之后住哪里?凡答不出这问题的「抛弃」,无论动机多现代,都是破坏。
1问题
上一章把不能动的列了清单。重写者拿到清单的第二反应通常是松一口气:「原来不能动的就这些?那能动的可太多了。」——这一章的职责,就是给这份松一口气加一道安检。
因为「可以抛弃」是一个比「必须保留」更容易误判的类别。必须保留的东西有社区的记忆守着,动它立刻有人喊;可以抛弃的东西往往安静——直到重写上线三个月后,人们发现某个「过时的老设计」原来悄悄托管的某条不变量已经无家可归。本章挑出四条最诱人的「可抛弃项」,逐条演示正确的抛弃姿势:**每抛一样,先安置它托管的不变量。**判定本章对错的办法很直接:按本章的方案抛弃任一项,若对应不变量安然无恙,本章得分;若不变量受损,本章的分析就是错的。
2语言运行时:抛 Java,安顿可移植与可反向
Java 是四条偶然里最常被提上重写议程的(第 14 章的双边结论是本章的地基)。抛掉它,三个不变量需要重新安置。
可移植性:当年 Java 用「一次编写到处运行」解决了独立游戏的分发问题。今天这个问题有了新答案——游戏引擎的跨平台抽象层、主机与移动商店的成熟、WebAssembly 等新的通用运行时。可移植性不变量可以搬家,且新家更宽敞。
可反向性(字节码可还原,社区因此读得懂、改得动游戏):这是真正需要慎重重安的一条。现代方案里,「开源核心」与「官方插件 API」是两条常见的替代路径,但第 8 章与第 13 章的教训必须挂在墙上:开源不等于承诺稳定,API 不等于覆盖全部需求——Java 时代社区享有的「无限反向权」是独一无二的,任何替代方案都要诚实承认这是降级,并说明为什么生态承受得起(Luanti 的全开源模式是「不降级」的一个存在证明,代价是内容生态从头自建)。
发布形态与历史可回访:这是最容易被漏掉的第三条。第 14 章讲过,jar 这种朴素的发布形态加上启动器的目录管理,让「一台电脑并存任意多个版本」近乎免费——卷一第 13 章那扇「旧存档的官方后门」,技术前提就是它。重写换用现代发布渠道(应用商店强制更新)之后,「回装任何历史版本」这个能力不会自动延续。它算不算不变量?卷一第 13 章的立场是算:历史可回访是「世界比引擎活得久」这条不变量的配套民生工程。重写方案里如果只有「最新版」而没有「任意旧版并存」,就是只搬了宫殿、烧了档案馆。
结论:语言可抛,可移植性必须续约,可反向性必须显式降级或加倍补偿,历史可回访必须写进新架构的需求文档第一页——它太便宜了,便宜到被忘记。
3全局单一时钟:抛 20 TPS 的统一节拍,安顿因果纪律
第 5 章把 20 TPS 立为契约,第 16 章记录了社区动土的尝试(Folia 的区域并行)。重写卷面对的问题更进一步:全局单一时钟是本质吗?
分析(第 16 章的「兴趣管理」视角已经铺好):全局时钟的本质功能不是「每秒二十次」,是因果的纪律——同一时刻的事件顺序确定、可复现、可依赖。这个纪律与「全局」没有必然关系:只要每个区域内部的因果有序、区域之间的接口定义明确(消息顺序、到达保证),多个局部时钟同样能维持纪律——代价是跨区域交互的复杂度与「全局同时性」的消失(两个区域的事件不再有严格的同时)。Folia 的探索证明了这条路的可行性边界:对分片明显的世界(玩家聚集点彼此远离)效果可观,对高交互密度场景(万人同城)挑战巨大。
结论:全局时钟可抛,因果纪律不可抛且必须逐场景论证——并行化不是性能开关,是把「顺序问题」换成「接口问题」的交易。签这笔交易前,先看看你的玩家有多爱跨区域互动。
这笔交易对红石玩家的具体意义,值得单独算给重写者看。全局时钟消失后,「一台机器横跨两个区域」从工程题变成外交题:机器的两半各自服从本区域时钟,接缝处的信号传递要走区域间接口——延迟、顺序、甚至可靠性都成了新变量。红石社区十几年的设计语言(整刻延迟、同刻联动)建立在「世界是一口钟」之上;局部时钟的世界里,这套语言要分裂成「区内语法」与「跨区语法」。也许会有新玩法从裂缝里长出来(区域间的「时差」本身就是新的物理),但重写者必须清楚:**你在要求一整个工程师社群重学母语。**这笔学费不在性能报表上,在社区的情感账户里。
4双版本分裂:抛第二个实现,安顿跨平台与商业模式
第 8、15 章写清了 Java 与 Bedrock 的分叉史与语义债。重写视角下,双版本是最明确的「可抛弃项」:统一代码库、多端发布——现代引擎的标准能力。但上一章四产品矩阵的提醒在此生效:Bedrock 线承载的不只是第二份实现,是一整套商业模式(Marketplace、主机审核、跨平台账号体系)。抛弃「两个实现」,真正要处理的是:两套社区的文化整合(互相看不顺眼十几年)、两个商店的合同现实、以及「统一」这个动作对既有两边玩家的重新定价(第 8 章的「利息还在涨」)。
还有一个历史视角必须诚实面对:**统一的机会窗口是开着还是已经关了?**Better Together(2017)其实是一次「半个统一」——它把所有非 Java 平台合并到了一份代码上,留下的分裂是「Java 对其余一切」。这证明统一的工程难度可控;难的是随后的语义与商业对齐:Java 玩家的自由与 Bedrock 玩家的秩序,两种社区契约没有互相吞并,而是各自加固。七年后,两边各自的存档、作品、习惯又厚了七层——统一的成本按第 8 章那条「利息」曲线继续上行。所以重写卷对这条偶然的判断要分两半:**架构上,统一毫无悬念;时机上,每一年的推迟都让合并的对价更高。**如果重写真的发生,它最大的红利恰恰是「以新引擎为借口完成合并」——新世界没有旧住户,也就没有拆迁纠纷;但新世界也留不住旧住户的记忆(旧档迁移的代价,第 9 章的兼容章接着算)。
结论:双实现可抛,且重写卷认为统一是四条偶然里收益最确定的一条——但它的代价不在工程在商务与社区,这不是技术卷能替谁拍的板。技术卷能说的是:统一后的引擎必须把 Java 版的「可反向性红利」与 Bedrock 版的「可管理性红利」同时安顿好,缺任何一个都会让一边的玩家觉得「被抛弃的是我」。
5实现细节的语义冻结:抛「怪癖」,先付清依赖
第 8 章讲过未定义行为成为 API 的四步过程。重写视角下这批东西最微妙:它们在技术上是纯债(无文档、无理由、阻碍重构),在产权上是纯资产(被依赖、被叙述、有教程)。
抛弃它们的正确姿势只有一个动作:先付清依赖,再拆除本体。「付清」的具体形态是官方提供等价物——观察者方块之于 BUD(第 8 章),是最成功的先例:官方没有直接修掉准连接的用法,而是给了社区一个「正门」元件,等正门消化了需求,旧门的存在就不再伤人。反例姿势也清晰:无替代的强制修正,就是卷二第 9 章的 1.9 战斗——社区不反对进步,反对的是没收。
把这个纪律翻译成重写工程的语言,是一份不该拖到后头的文档:怪癖替代物需求清单。重写立项时,就该为每一个被依赖的实现细节登记三行信息:它被依赖到什么程度(教程量、机器量、术语量);它的「正门」等价物是什么、是否已存在;过渡期的双轨策略(新旧并存多久、何时拆旧门)。这份清单在原版的世界里从来没有被正式写过——官方靠 Wiki 与社区的舆情间接感知依赖;重写项目有一张白纸,没有理由不把隐性契约写成显性条款。第 11 章(谁有权重写)会回到一个相关的追问:这份清单的最终审定权归谁?答案暗示着本章最后的分寸:技术团队可以决定「怎么修」,只有社区能决定「可以拆什么」。
结论:怪癖可抛,但抛弃的成本公式是「等价替代物的开发与推广成本」,不是「删除那几行代码的成本」。重写预算里没列这一项的,预算就是错的。
6这一章留下什么
- 爱好者:这份清单反过来读就是一份「情怀避雷指南」——当一个重写项目说要「现代化」,先问它四条偶然的对应不变量搬去哪了。
- 开发者:能抄走的是抛弃的安检流程——每一项「过时设计」先列出它托管的不变量,再设计新居所,最后才轮到拆除;以及「替代物先行」的拆除纪律(观察者先例)。抄不走的是判断「哪些依赖已经成网」所需要的那个东西:一份足够老、足够大的社区——它才是依赖清单的原始出处。