Discworld MUD (FluffOS bundle, v3)

✅ 可玩

Discworld

discworld

更新 68cad6f 2026-09-05 源码 下载 ZIP

▶ 开始游玩 · Play Now

The official Discworld MUD's mudlib, as redistributed by Cratylus (Dead Souls' maintainer) in a self-contained "Discworld Bundle" paired with a matching FluffOS build (dw_fluffos_v3 is the latest of three snapshots this project holds; v1/v2 are independently converted as libs/dw_fluffos_v1 and libs/dw_fluffos_v2). One of the longest-running, most-studied LPMuds in existence (running continuously since 1991), set in Terry Pratchett's Discworld. New characters start in the famous circular Discworld Room in Ankh-Morpork's Cabbage Warehouse Rooming House, guided in by a wandering "womble" NPC. Uses a directory-based command dispatcher (a verb like `look` resolves to a file `cmds/living/l_ook.lpc`, the underscore marking its minimum abbreviation) layered under a `drunk_check()`/`lower_check()` eval-cost and command-queueing throttle.

README

The official Discworld MUD mudlib, set in Terry Pratchett's Discworld and running continuously since 1991 -- one of the longest-lived, most widely studied LPMuds in existence. This copy comes from Cratylus's (the Dead Souls maintainer's) unofficial "Discworld Bundle" v3 redistribution, which pairs the mudlib with a matching FluffOS build (we use this project's own driver instead, per usual). This project also holds independently converted v1 and v2 snapshots of the same bundle (libs/dw_fluffos_v1/ port 40271, libs/dw_fluffos_v2/ port 40272).

Highlights

Registration flow

N (new character) -> character name -> confirm (y/n) -> password -> confirm password -> capitalisation (accept the default with Enter, or type your own) -> gender (male/female) -> a Terms and Conditions screen, which the server deliberately pauses ~30 seconds before showing the final yes/no prompt for (a real server-side call_out, not a client hang -- just wait it out) -> yes -> you arrive in the Discworld Room.

Admin account

Registered through the normal registration flow like any other character. Discworld's admin model layers a hardcoded TRUSTEES macro (in secure/master.lpc, containing Root and the original archive's cratylus) on top of a real, persisted positions mapping saved in secure/master.o -- and since granting trustee rank normally requires already *being* a trustee (add_trustee() checks the caller), the bootstrap grant was seeded directly into that save file: positions (["fluffos":2,]) (2 is master.lpc's own TRUSTEE constant). Verified live: wizard-only login banners appear, and a wizard-level recompile command (compile <path>) passes its ACL check cleanly (it hits an unrelated pre-existing bug further in, documented in NOTES.md, not a permission denial).

Status

Boots clean (zero compile errors in debug.log). Fresh registration, look, score, and quit all verified end-to-end with a real driver session, including the initial automatic room description on arrival and a full server-driven quit sequence. Re-login (the restore path) also verified. See NOTES.md for the full list of driver-compatibility bugs found and fixed during this port -- the headline one: this codebase's add_action(fn, "*", ...) catch-all convention doesn't exist on this driver (the catch-all verb here is ""), so every single command silently failed until that was fixed across five call sites, alongside a separate [0..<3] fixed-width extension-stripping bug in secure/command.lpc that broke the directory-based command dispatcher the same way. A few genuine content gaps in this third-party archive (a missing /std/outside.lpc base class, missing /obj/armours/ and /obj/clothes/ content directories, no MySQL server available for the map/pathfinding subsystem) are documented in NOTES.md rather than invented or patched over.

WASM status: playable. Full registration through to the Discworld circular hall is verified under the shared WASM driver (NOTES.md WASM section). Play: https://mudlibs.fluffos.info/discworld/

NOTES · 移植与修复记录

概述

discworld(901)是官方 Discworld MUD 的 mudlib,本次转换自 archives/901-2_dw_fluffos_v3_dw_fluffos_v3.zip(Cratylus 打包的 "Discworld Bundle" 第三版,随附一份匹配的 FluffOS 驱动源码——按 AGENTS.md §2 惯例忽略捆绑驱动,只用本项目自己的驱动)。这是本项目 第一个真正意义上的纯英文 mudlib(此前"deprioritize English libs" 的旧策略已被项目负责人撤销)。archives/ 里同时还有 v1 (901_dw_fluffos_v1_dw_fluffos_v1.tar.gz) 和 v2 (901-1_dw_fluffos_v2_dw_fluffos_v2.zip) 两份更早的快照,未发现 v3 有 v1/v2 没有的结构性问题,因此直接采用 v3 作为正式转换对象; v1/v2 已按用户指示单独转成 libs/dw_fluffos_v{1,2}/(编号 901-1/901-2,端口 40271/40272),不是这份 v3 work/ 的静默 overlay。 主编号 901 仍是这份真正转换的 libs/discworld/

编码

英文 mudlib,GB18030→UTF-8 转档步骤基本不适用(convert_lib.sh 的 "already_utf8" 分支覆盖了绝大多数文件)。但发现并修复了三个真正的 编码坑,跟 AGENTS.md §4.1 描述的"GB18030 静默误转"是同一类问题, 只是编码对不是 GBK/BIG5,而是 Mac Roman(老 Mac 文字编辑器留下 的遗产,符合这份档案 1990 年代初 Discworld 开发史的年代背景): doc/lpc/intermediate/chapter1doc/concepts/conversions (两份游戏内 LPC 教程文本)、www/external/java/telnet/README (bundle 自带的 Java telnet 小程序说明,非 mudlib 运行时内容)。 convert_lib.sh 默认的 GB18030 转码对这三个文件要么产出明显乱码 (conversions 被标记为 lossy),要么"成功"但内容变成 garbled 的 合法 UTF-8(chapter1 静默出现 mud'smud誷;这正是 §4.1 警告过的、GB18030 广义超集特性带来的静默误转类别,只是这次的源编 码是 Mac Roman 而不是 BIG5)。用 Python 对整个 work/ 树做了一次 全量 UTF-8 可解码性扫描,逐一用 mac_roman/cp1252/latin-1 试 解码确认后,从 raw/ 原始字节用 mac_roman 重新解码替换修复。

.c.lpc 重命名的连锁反应(AGENTS.md §4.2 类,本 lib 最集中

的 bug 来源)

secure/command.lpc:同一个定长切片 bug,但后果是"每一条指令都

失效"

这是本次修复里影响面最大的一个:eventRehash()/ eventGuildRaceRehash() 扫描 cmds/{living,player,creator,...}/ 目录下的 *.lpc 文件建立指令名索引,原文件对每个文件名做 file[0..<3] 去掉 .c 后缀(比如 l_ook.cl_ook,下划线是 Discworld 自己的"最短缩写标记"约定)。convert_lib.sh 已经把 get_dir(...) 的通配符从 "/*.c" 改成了 "/*.lpc"(这是被 ref-fixup 扫描到的引号字符串,能自动修),但紧接着的 [0..<3] 是纯算术定长切片,扫描不到。结果是索引表里存的键从 "l_ook" 变成了带着半截扩展名尾巴的 "l_ook.l",任何真实指令名 都查不中——look/get/drop 等等全部指令,不管是自动触发的还是 玩家手打的,全部落到驱动的默认 fail message "What?"。改成 [0..<5](去掉 4 字符的 .lpc)后修复,同时把两处几乎一致的 调用点都改了并加了详细注释解释切片算术。

add_action"*" 万能动词约定,在这个驱动上根本不存在

(AGENTS.md §6.2 类,本次深挖最有价值的发现)

这份 mudlib 出身的原始驱动把 add_action(fn, "*", priority) 当成 "匹配任意指令"的万能动词写法,并且把第三个参数当优先级数字用(从 -1000010000 都有)。整份代码库有 5 处这样的注册: std/living/living.lpcexit_command(出口/移动检测)、 global/psoul.lpclower_check/drunk_check(eval-cost 节流 和指令排队)、global/new_parse.lpcnew_parserglobal/command.lpccmdAll(真正的目录式指令分发器,见上一 节)。

但本项目这份 FluffOS 驱动(packages/core/add_action.cc)里 add_action 的动词匹配是精确字符串比较(或前缀匹配,取决于标 志位),源码里明确写着"if was add_action(blah, "") then accept it"——**万能动词约定用的是空字符串 "",不是 "*"**(驱动自带的 testsuite/clone/user.lpc 也是这么用的)。字面上的 "*" 不会被 特殊处理,就是个永远不会等于任何真实指令的普通字符串。后果:这 5 个处理函数在这个驱动上一次都不会被调用——不是报错,是彻底静默 失效。用 efun::write()(§10.3 建议的、能绕过 write_file() 潜在 ACL 拒绝的调试手段)在 command_commands()/cmdAll() 里加临时探针才 现场坐实:command_commands() 确实执行了、add_action 也确实调用 了、commands() 里也确实能看到 5 条以 "*" 为动词的记录——但玩家 打 lookcmdAll 的探针从未触发过,"What?" 直接从驱动兜底逻 辑冒出来。全部 5 处 add_action(fn, "*", N) 改成 add_action(fn, "", N)(连同两份未被实际 inherit 的历史备份文件 std/living/living.eff_shad.lpc/living.no_eff_shad.lpc 里的同款 调用,为了一致性也顺手改了,虽然它们目前不在真正的继承链上)。

同一个坑的第二层,不改完全部指令依然会崩:这个驱动上 add_action 的第三个参数根本不是优先级,而是一个 2-bit 标志位 (flag & 3V_SHORT=1V_NOSPACE=2,见 vm/internal/simulate.h),决定驱动传给处理函数的参数是"整行原 文"还是"undefined"。原代码库那些看似"优先级"的数字被这个驱动重新 解释成了几乎随机的标志组合:cmdAll 恰好拿到 -1&3=3(含 V_NOSPACE,侥幸拿到整行文本)、new_parser 拿到 -2&3=2(同样 侥幸),但 exit_command1&3=1,只有 V_SHORT)、 lower_check/drunk_check±10000&3=0,两个标志都没有)在这个 驱动上对不带空格的单字指令(比如 look)拿到的参数是 undefined 而不是字符串 "look"。现场验证:只改动词字符串后重新开机,look 不再是 "What?",但会在 exit_command() 内部因为 explode(word," ")word==0*Bad argument 1 to explode() 崩溃(这是这些处理函数第一次真正被这个驱动调用,之前从未被真实执 行过,所以这个参数类型 bug 此前完全没有暴露的机会)。把全部 5 处 的第三个参数统一显式改成 2V_NOSPACE),确保驱动总是把整行原 文传给这些处理函数——这才是它们的代码本身一直假设的行为。两处改动 (动词字符串 + 标志位)叠加后,look/score/quit 等全部指令才 真正跑通。

secure/simul_efun.lpcquery_multiple_short 的先有鸡先有蛋

std/object.lpc 等大量文件把 query_multiple_short() 当 simul_efun 裸调用。原代码库自带一份可用的 polyfill (secure/simul_efun/multiple_short.lpc),但在 secure/simul_efun.lpc 里被注释掉了,附注"现在通过 parser 包提供"——说明原始目标驱动把它 实现成了真正的 C 层 efun。这个驱动没有这个 efun,取消注释后仍然编 译失败:secure/simul_efun/modified_efuns.lpc(在 secure/simul_efun.lpc 里比 multiple_short 更早被 inherit)内部 调用了 query_multiple_short(),但每个 inherit 目标文件是被当独 立编译单元处理的——simul_efun 对象自身还在"第一次构建中",驱动尚未 把它注册为可解析的 simul_efun,所以裸调用在这个阶段无法按 simul_efun 兜底解析,报 Undefined function query_multiple_short。修法参考 同一个文件里已有的先例(base_name 就是用同样手法处理的):让 modified_efuns.lpc 自己直接 inherit "/secure/simul_efun/multiple_short", 这样它在调用点之前就作为真正的同编译单元继承函数存在,不再依赖 simul_efun 运行时解析。

global/psoul.lpcprocess_input()time_expression{}

按分支不对称收尾

process_input()int t = time_expression{ ...; #if efun_defined(add_action) return str; #else _process_input(str); #endif }; 这种写法,但闭合花括号 }; 只出现在 #else 分支内部,#if 分支(return str;)完全没有闭合。在原始目标驱动上 add_action 不是原生 efun,#else 分支恒定被选中,从未暴露过这个不对称。这个 驱动上 add_action 是原生的,#if 分支被选中,time_expression{} 块(以及整个 process_input() 函数)实际上从未正确闭合,把后面几 百行代码(_process_input/command 函数定义、lower_check() 等) 都错误地解析成了 process_input() 内部悬空语句,最终在文件末尾的 int lower_check(...) 处以 unexpected L_BASIC_TYPE 报错——报错位 置离真正的病灶(一次 #if/#else 分支闭合不对称)隔了 100 多行, 容易误判成 lower_check() 自己的语法问题。修法:把 }; 挪到 #endif 之后,两个分支统一在同一处闭合;#if 分支的 return str; 依然会在闭合前提前退出函数,行为不变。

global/wiz_channels.lpc:命名参数 lambda 里混用了 $1

map_func = function (object ob) { ...; str = $1->query_cap_name(); ... } 用显式命名参数 ob,函数体里却写成匿名闭包才用的 $1——这 个驱动对这种混用直接报编译错误($var illegal inside anonymous function pointer),大概率是从紧邻的 (: strcmp($1->query_name(), $2->query_name()) :) 匿名闭包写法复制粘贴时漏改的作者笔误。把三处 $1 全部改成 ob

obj/handlers/armoury.lpc:目录缺失时的空指防护

walk_directory()get_dir(dir,-1) 的返回值不做非数组判断直接 foreach;这份第三方压缩包本身就缺 /obj/armours//obj/clothes/ 两个内容目录(见下面"已知内容缺口"),get_dir() 对不存在的目录返回 0 而不是空数组,每次 rehash("armours"/"clothes") 都会崩一次 *Bad argument 2 to foreach(被 preload 的 catch 兜住, 不影响开机,但会持续往 log 里写噪音)。加了 foreach(file in (tmp || ({}))) {...} 防护,属于通用防御性写法, 和目录缺失是否"该修"无关——不管以后是否补上这两个内容目录,这个 guard 都是对的。

预加载卫生(AGENTS.md §7.6 标准策略)

secure/config/preload 里的 /net/intermud3/intermud(真实的 Intermud-3 协议精灵,会试图连接外部 intermud 路由器)按标准策略提 前注释掉,避免在沙盒环境里挂起/拖慢开机。它的 create() 本身不会 立即联网(真正的 socket 连接是懒加载的),只有玩家主动执行 mudlist 才会隐式自动编译并触发;这不在核心验证路径上,未做进一 步处理。

管理员账号播种

secure/master.lpc 用一个硬编码的 TRUSTEES 宏(含 Rootcratylus)+ 一个真正可持久化的 positions mapping(存在 secure/master.o 里,通过 add_trustee()/add_senior() 等指令维 护)两层机制判断管理员身份。positions 在原始档案里是空 mapping, 且 add_trustee() 本身要求调用者已经是 trustee 才能提升别人(先有 鸡先有蛋,符合 §1.5 提到的典型引导场景)。直接编辑 secure/master.o 的存档数据,把 positions (["fluffos":2,]) 写进去(2master.lpc#define TRUSTEE 2 的值)。验证:fluffos 正常走 完注册流程 → 密码验证成功注册 → 重新登录时能看到只有巫师才会看到 的"To all creators.../To all Domain Leaders..."提示横幅 → 执行 compile /std/room(本 lib 里 wizard 级别的重编译指令,等同其它 lib 常用的 update)成功走到实际尝试编译的阶段(报 Undefined function generate_source,而不是任何权限拒绝提示——说 明 ACL 授权本身是通的,卡在的是下面这条独立的、和管理员授权无关的 预置代码 bug)。

已知内容缺口(第三方重打包档案本身缺失,非本次转换造成,不予

"修复")

验证记录

WASM

2026-08-25 更新(另一次 session):真正跑通了 WASM 通道,wasm_status""(未测试)提升为 playable。用的是 CI 同款的预编译 WASM 驱动 release(fluffos/fluffos*-wasm.zip),配合 scripts/pack_lib_for_web.sh + scripts/wasm_boot_check.js / scripts/wasm_client.js 本地复现。

首次打包后驱动完全无法启动——simul_efun 是唯一"急切"(非懒惰)加载 的对象,而它下面的 secure/simul_efun/dump_socket_status.lpc 无条件 调用了这台驱动没有的 socket_status()(WASM 构建没有 sockets package),导致 *No program in object '/secure/simul_efun/...'!、 整个驱动拒绝启动——和同一个 session 早些时候在 ds386(Dead Souls) 上发现并修复的问题是同一类根因,详见 AGENTS.md §7.52 那条追记。 按同样的套路把 dump_socket_status() 挖空成安全桩后,又连续撞上 compress package 缺失的两处编译错误(secure/master/ create_dom_creator.lpc 里 6 处 compress_file()/uncompress_file() 闭包引用、secure/login.lpc/obj/handlers/login_handler.lpc/ global/player.lpc 里 3 处 compressedp()),逐一挖空/常量化后驱动 终于干净启动、能接到连接。

完整验证:真实跑完一遍注册向导(含文档里提到的、服务端故意暂停 ~30 秒的条款确认环节——用 wasm_client.js--idle 参数需要设得 比这个暂停更长,否则 yes 会在暂停期间过早发出而落空,这是脚本 本身的坑,不是驱动 bug),成功进入著名的 Discworld 圆形大厅,look 输出与原生测试记录的房间描述完全一致,MCCP 提示("You are logged in uncompressed!")也正确出现(验证了 check_mccp()compressedp() 桩替换按预期工作),womble NPC 的环境对话正常播放。quit 本次因为 womble 持续产生环境消息、脚本的 idle 检测一直"看不到真正的安静"而 没能在这次转录里捕获到,但 quit 路径本身没有触碰任何 compress/ socket 相关代码,原生测试已经完整验证过,不需要重复。

另发现一个非致命的预加载期编译失败:/net/daemon/board_thingy (继承自 /net/inherit/server.lpc,一个 BBS/公告板网络监听服务) 同样因为 sockets package 缺失而编译失败,但这不影响驱动整体启动 (预加载会跳过失败的对象继续走),也不在登录/游玩路径上——按这个 项目"只修必经路径,可选管理工具留作已知缺口"的既定做法,本次没有 处理,留给以后如果真的有人报告这个 BBS 功能坏了再修。

本 session 的一个失误(如实记录)

排查驱动进程时先后两次误用了匹配范围过宽的 kill:一次是用精确 PID kill 时没有先核实该 PID 属于哪个工作目录,误杀了另一个并发 session (libs/ds386)正在运行的驱动;另一次是用 pkill -f "driver config.fluffos" 排查端口占用,这个文件名是全项目几乎所有 lib 共用 的约定命名,极可能同时误杀了当时并发运行的 nightmare3/lima 两个 lib 的驱动进程(进程列表在那次 kill 前后确认消失)。两次都不 是数据/文件层面的损坏,只是让对应 session 的驱动测试被迫中断重启; 已按项目惯例(“Kill drivers by exact PID / never pkill -f”)改回精 确 PID 方式,后续操作未再复发。

深度功能测试(round two,2026-08-27)

本次是 discworld 第一次真正的 §10.7 round-two 全流程游玩测试(此前 NOTES.md 里的"验证记录"一节只是转档时的安装期烟雾测试,没有 深度功能测试 标题,AGENTS.md 的 §10.7 完成度统计里从未把这份 lib 计入)。用真实驱动(~/src/fluffos/build-debug/src/driver config.fluffos,端口 40206)连续开了 6 次机(每次都是干净重启, grep -c "error:" debug.log 恒为 0),全程用一份 Python 原始 socket 脚本(dw_client.py)交互,测试角色 Roundtwo(真实注册 流程,含服务端强制的 ~30 秒条款确认暂停)。

发现并修复的 bug

obj/handlers/armoury.lpcrequest_item() 在找不到道具时返回 0(有文档、有先例:早前转档时 walk_directory() 就因为同一个 "目录缺失返回 0 而不是空数组"的根因加过防御性 guard),但代码库里 至少 5 处 ARMOURY->request_item(...)->move(...) 调用链完全没有判 空,直接对返回值做 ->move() 由于这份 "distribution lib" archive 本身缺失 /obj/clothes//obj/armours/ 两个内容目录(转档时已 记录的已知缺口),任何请求这两类道具的调用都会返回 00->move() 在这个驱动上报 *Bad argument 1 to call_other()(未捕获),把调用 方的 create()/setup() 从崩溃点截断。

live 复现:管理员测试角色进入新手战斗训练室(d/liaison/NEWBIE/ combat.lpc)后尝试 one/two/three 三个训练间任一个入口, combat_room1.lpc(等)会 clone_object 训练假人 d/liaison/NEWBIE/dummy.lpc,假人 setup()ARMOURY->request_item("dirty rags", 30)->move(this_object()) 崩溃(dirty rags 是缺失目录里的衣物),玩家侧看到"A runtime error occurred.",log/runtime 记录 *Bad argument 1 to call_other() (未捕获,直接从驱动兜底冒出)。这条路径正是本轮方法论第 3 步要求 的"safe-sparring 机制",属于核心验证路径,不是可选管理工具。

修复:给全部 5 处受影响的调用改成"先存局部变量再判空"写法(不改变 道具存在时的行为,只是让缺失时优雅跳过而不是崩溃),跟转档时 walk_directory() 的修法同一个思路:

刻意没有动的同款代码:d/learning/cutnpaste/althea.lpc(教学沙盒 目录下的"抄写练习"示例 NPC,就是用来教这个确切写法的,不是真实 游玩内容)以及两份 .htm/.txt 教程文档——按项目一贯的"内容/文档不 在修复范围"处理。

新的 AGENTS.md bug 类:这是 §7.6"缺失目录"系列的一个新变种—— 不是 get_dir()/read_file() 返回 0 被当数组用,而是一个有据可 查、文档明确写着"找不到就返回 0"的工厂函数(request_item()), 它的返回值被链式 ->move() 调用,中间没有存局部变量、也没有判空。 由于道具类文件层层散布在整个代码库里,这类调用天生就是"批量隐患" 的形状——同一个安全 factory 函数,调用方各自决定要不要判空,任何一 个偷懒的调用方在对应内容缺失时都会阻断自己的 create()。已在下面 新增 AGENTS.md §7.147 记录这个模式,供以后其它 archive 遇到同类 "factory-on-missing-content returns 0" + "unguarded ->call chain" 组合时参考。

std/shops/print_shop.lpcadd_auto_load_info() 用驱动保留字 nosave 当参数名,整个印刷厂片区(print_shop.lpc 自己 + 它的四 个子类 print_shop_office/print_shop_foyer/print_shop_press/ print_shop_binding)在这个驱动上完全无法编译,从转档以来一直是 死代码。 nosave/static 在这个驱动的词法分析器里都是真正的 L_TYPE_MODIFIER 保留字(~/src/fluffos/src/compiler/internal/ lexer_utils.cc),protected int add_auto_load_info(string nosave, string dynamic); 这行原型声明里把 nosave 当参数名用,编译器直接 报 error: syntax error, unexpected L_TYPE_MODIFIER——不是我这次 交互测试里现场触发的(这个函数从没被玩家路径直接调用过),是用 lpcc 单文件编译核对旁边一处不相关问题时顺带发现的:查 log/error-log.old 发现同一行报错从转档以来的历次开机日志里反复 出现过至少 7 次,说明这从来没被人注意到过。函数体内部 $(nosave) 这个 lambda 捕获、连同文档注释里其实早就写着 @param static_arg 而不是 nosave(暗示原作者自己心里想的参数名 跟实际写的不是一回事,大概率是笔误)。修法:把参数名从 nosave 改成 nosave_arg(跟文档注释的命名意图一致),原型声明和函数定义 两处、函数体内部的 $(nosave) 引用一并改。用 lpcc 核对:修复前 std/shops/print_shop.lpcd/dist/pumpkin/rabbit/ print_shop_office.lpc 单独编译都直接报错;修复后两个都干净通过 (无 warning 之外的输出)。全库 grep 过其余全部 nosave/static 两个保留字被当裸参数名用的情况,只有这一处命中。已新增 AGENTS.md §7.148 记录这个模式。

检查过、确认干净的标准 bug 模式(§10.7 清单里列出的横切模式)

新观察:这是一份"distribution lib"裁剪版,公会系统本来就没有随

包分发(不是 bug,如实记录)

include/config.h 定义了 __DISTRIBUTION_LIB__d/liaison/NEWBIE/ path.h 在这个宏打开时把 GUILDS 宏定义成字面量 "None currently"d/liaison/NEWBIE/guilds_foyer.lpc 里全部六个公会大门出口 (witch/wizard/thief/assassin/warrior/priest)都包在 #ifndef __DISTRIBUTION_LIB__ 里——也就是说这份 archive 设计上就 不随包分发任何公会加入内容/std/guilds/ 目录下也确实只有 warrior.lpc/standard.lpc 两个基类文件,没有任何一个公会总部房 间(d/guilds/ 整个目录都不存在;开机 preload 列表里的 /d/guilds/ wizards/books/beginners/d/guilds/wizards/Ankh-Morpork/inside/ gymnasium/d/guilds/wizards/chars/frenkel 三条也全部指向不存在 的文件,被 preload 的 catch 静默吞掉,零 debug.log 痕迹)。这意味着 本轮方法论第 4 步(公会/技能习得路径测试)在这份具体 archive 上 从设计上就不可达,不是我漏测——如实记录,不视为 bug,不尝试编造 公会内容去"修复"。

顺带发现一个由此衍生的真实崩溃(同一根因,不单独归为新 bug):地 图上"迷路仙女教母"NPC(magrat.lpc/granny.lpc)的 setup() 都 调用了 set_guild("witch"),而 /std/guilds/witch.lpc 在这份 archive 里根本不存在,std/race.lpc:199set_level() 内部对它 做 call_other() 直接报 *call_other() couldn't find object '/std/guilds/witch'(live 复现,log/runtime 有记录)。同样属于 "distribution lib 没有公会内容"这一句话能完全解释的已知限制,不修。

另外,d/liaison/NEWBIE/foyer.lpcguilds 出口本身也会崩 (d/liaison/NEWBIE/guilds_foyer.lpc inherit PATH+"outside" -> d/liaison/NEWBIE/outside.lpc inherit "/std/outside" -> 转档时就已经记录在案的已知缺口"/std/outside.lpc 完全不存 在"),live 复现:*Inherited file '/std/outside' does not exist!log/runtime)。这是对已有已知缺口条目的一次扩大确认——此 前的记录只提到"多个教学/示例房间和至少一个真实房间(room/air.lpc)" 受影响,这次确认它还directly 挡住了新手大厅九个出口里唯一通往 "guilds"(公会花园)的那一个,即使 /std/outside.lpc 真的补上了, 这个花园本身在这份 distribution 配置下也只有一个 "foyer" 返回出口 (六个公会大门出口全部被 #ifndef __DISTRIBUTION_LIB__ 排除),没 有任何实际可加入的公会内容——两个独立限制(缺失基类 + 故意裁剪的公 会内容)叠加在同一个房间上。仍然判定为"已知内容缺口,不予修复"(需 要编造一整个 /std/outside.lpc 基类文件的内容,属于内容/设计猜测, 不是程序 bug),只是把这条记录写得更完整。

环境/测试基础设施问题(非 mudlib 代码 bug,直接修复)

已验证的核心流程(本轮 round-two 清单)

管理员账号重新播种

上一次转档 session 种下的 fluffos 管理员角色存档本身没有随仓库提 交(save/players/ 整棵树不在 git 跟踪范围内),这次沙盒环境里已 经找不到了(secure/master.opositions trustee 记录本身还在, 只是玩家存档丢了)。用同样的账号名 fluffos/密码 Mud@2026 走完 整套注册流程,真实挂机 30+ 分钟(满足 global/player.lpcMIN_TIME_TO_SAVE=1800 秒新手存档门槛)后 quit,验证 save/players/f/fluffos.o.gz 已生成且 #/global/lord.lpc 开头(确 认是真正的 creator/lord 类实例,不是普通玩家),全程 debug.log/ log/runtime 干净。

商店切片(2026-09-04)

补完 2026-08-27 §10.7 标成「list 时序、未完全验证」的商店。根因 不是 soul 排队:是 command() efun 阴影(v1/v2 本轮已修;v3 living.lpc 早已有 #if !efun_defined(add_action))。本轮不再重挖 那一处。

端口 40206global/newbie_junk.lpc 仍把 8 Pumpkin dollar / 100 Pumpkin pence 放在未判空的 request_item("bucket small") 之后, 已按 v1/v2 改成先发钱再对 armoury/clone 判空。money_handler.lpc 同样缺档案里的 /save/money_handler.o,补了与 v2 相同的 Pumpkin / Newbie Area 币种表(比值来自本树 money_symboliser,P$1 == 100)。

冷启动后南瓜菜单 N 注册 shoplite / Play2026xcommerce 立刻 打出商店房间。同一会话只发一条 buy a lightable torch(中间出现 Queued command: buy a lightable torch,约十余秒后刷出回执): You buy a lightable torch for 50 Pumpkin pence coins.

2026-08-27 的「list 时序」不再算未完成。没有拜师(distribution 公会 内容未随包)。