info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
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
- New characters start in the famous circular Discworld Room, in the Cabbage Warehouse Rooming House in Ankh-Morpork -- a large stone table at its centre bears a map of the world resting on Great A'Tuin and the four elephants, exactly as the books describe it.
- A wandering "womble" NPC greets new arrivals and wanders the starting rooms delivering flavour dialogue -- Discworld's own idiom for "an NPC that moves/does something" is literally the verb "womble".
- Commands are resolved through a directory-based dispatcher: a verb like
lookmaps to a filecmds/living/l_ook.lpc, where the underscore marks the shortest legal abbreviation (l). Every command passes through adrunk_check()/lower_check()throttle first, which can transiently queue a command ("Queued command: look") for a moment right after login while your character's initial time budget catches up -- this is normal, not a bug. quittriggers an in-character send-off (a gecko checks your inventory, then a chimera or giant bird carries you off) rather than an instant disconnect.
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
- id:
fluffos - password:
Mud@2026 - rank: full trustee/administrator (Discworld's own
TRUSTEErank, the same levelquery_trustee()/query_administrator()/high_programmer()all check)
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_discworld_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/chapter1、doc/concepts/conversions
(两份游戏内 LPC 教程文本)、www/external/java/telnet/README
(bundle 自带的 Java telnet 小程序说明,非 mudlib 运行时内容)。
convert_lib.sh 默认的 GB18030 转码对这三个文件要么产出明显乱码
(conversions 被标记为 lossy),要么"成功"但内容变成 garbled 的
合法 UTF-8(chapter1 静默出现 mud's → mud誷;这正是 §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 来源)
global/virtual/virtual.h自包含:原文件是#include <virtual.h>(尖括号,通过 include 路径解析到/include/virtual.h,一份内容完全不同、真正定义VIRTUAL_NAME_PROP等宏的文件)。convert_lib.sh的"本地尖括号 转双引号"启发式误判:因为同一目录下恰好也有一个同名文件global/virtual/virtual.h(也就是它自己!),把它错误地改写成#include "virtual.h"——变成字面意义上的自我包含,导致#include nested too deeply。已改回尖括号形式,并确认全库范围内 这是唯一一处真正的自包含碰撞(写了个脚本核对每个本地#include "X"的解析目标是否等于包含它自身的文件,只有这一处命 中)。- 虚拟对象编译器的后缀分发表(
global/virtual/server.lpc的create_virtual_object()):按文件名最后一段(explode(name,".")取最后一个元素)查表决定用哪个编译方法,原来只注册了"c"/"C"两个后缀作为"普通 LPC 主文件"的兜底编译器。重命名后这类引用变成.lpc后缀,查表落空,报create_virtual_object() : unknown suffix to /obj/vessel.lpc。已 在server.lpc的create()里补上add_method("lpc", ...)作为"c"/"C"的别名。 - 道具数据文件里硬编码的
.c基类引用(78 个.ob文件):这 类"prop 文件"不是.lpc/.h,convert_lib.sh的引用修复只扫描 这两种扩展名,所以类似obj/misc/buckets/bucket_small.ob里的::#Class:: "/obj/vessel.c"完全没被碰到。虚拟对象编译器 (global/virtual/compiler.lpc的prop_to_fun()) 会把这个#Class值原样拼进生成的临时源文件里当create_virtual_object()的参数,指向一个磁盘上已经不存在的.c路径,报*Bad argument 1 to EFUN call_other() ... Got: int(0)(新角色出 生装备里的水桶就是这么崩的)。写了个脚本扫描全部.ob文件里的::#Class::/::#class::行,把其中的"X.c"批量改成"X.lpc",78 个文件全部命中修复(grep -c复查后 0 剩余)。 secure/command.lpc的定长切片(本 lib 最严重的一个 bug, 见下面单独一节)。- 虚拟基类的
__FILE__定长切片:obj/armour.lpc、obj/clothing.lpc、obj/shield.lpc、obj/clothing_transport.lpc的query_static_auto_load()都有if (base_name(this_object()) != __FILE__[0..<3]),用来判断"我是 不是这个基类本身而不是子类/克隆体"。__FILE__原本是.c后缀 (2 字符),[0..<3]精确去掉 2 个字符;重命名后__FILE__变成.lpc(4 字符),仍然只去掉 2 个字符,导致比较永远不相等,query_static_auto_load()对这四个基类永远返回空 mapping 而不是 真实的静态装载数据。已改成[0..<5](去掉 4 个字符)。同一 codebase 里还有几处类似切片(secure/bulk_delete.lpc、cmds/creator/hous_ing.lpc、obj/handlers/autodoc/*.lpc、global/cloner.lpc的add_mapping())没有确认是否受影响——都是 巫师/管理工具,不在本轮 look/score/quit 核心验证路径上,留给以后 的 pass。
secure/command.lpc:同一个定长切片 bug,但后果是"每一条指令都
失效"
这是本次修复里影响面最大的一个:eventRehash()/
eventGuildRaceRehash() 扫描 cmds/{living,player,creator,...}/
目录下的 *.lpc 文件建立指令名索引,原文件对每个文件名做
file[0..<3] 去掉 .c 后缀(比如 l_ook.c → l_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) 当成
"匹配任意指令"的万能动词写法,并且把第三个参数当优先级数字用(从
-10000 到 10000 都有)。整份代码库有 5 处这样的注册:
std/living/living.lpc 的 exit_command(出口/移动检测)、
global/psoul.lpc 的 lower_check/drunk_check(eval-cost 节流
和指令排队)、global/new_parse.lpc 的 new_parser、
global/command.lpc 的 cmdAll(真正的目录式指令分发器,见上一
节)。
但本项目这份 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 条以 "*" 为动词的记录——但玩家
打 look 时 cmdAll 的探针从未触发过,"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 & 3;V_SHORT=1、V_NOSPACE=2,见
vm/internal/simulate.h),决定驱动传给处理函数的参数是"整行原
文"还是"undefined"。原代码库那些看似"优先级"的数字被这个驱动重新
解释成了几乎随机的标志组合:cmdAll 恰好拿到 -1&3=3(含
V_NOSPACE,侥幸拿到整行文本)、new_parser 拿到 -2&3=2(同样
侥幸),但 exit_command(1&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 处
的第三个参数统一显式改成 2(V_NOSPACE),确保驱动总是把整行原
文传给这些处理函数——这才是它们的代码本身一直假设的行为。两处改动
(动词字符串 + 标志位)叠加后,look/score/quit 等全部指令才
真正跑通。
secure/simul_efun.lpc:query_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.lpc:process_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 宏(含 Root 和
cratylus)+ 一个真正可持久化的 positions mapping(存在
secure/master.o 里,通过 add_trustee()/add_senior() 等指令维
护)两层机制判断管理员身份。positions 在原始档案里是空 mapping,
且 add_trustee() 本身要求调用者已经是 trustee 才能提升别人(先有
鸡先有蛋,符合 §1.5 提到的典型引导场景)。直接编辑 secure/master.o
的存档数据,把 positions (["fluffos":2,]) 写进去(2 是
master.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)。
已知内容缺口(第三方重打包档案本身缺失,非本次转换造成,不予
"修复")
/std/outside.lpc完全不存在(原始raw/里也没有)。多个 教学/示例房间(d/learning/...)和至少一个真实房间 (room/air.lpc)inherit "/std/outside",这个基类缺失会让这些 房间编译失败。/std/outsides/(复数)目录下有basic.lpc等一 套不同的"地形贴图"体系,但它们各自inherit TERRAIN_MAP_OUTSIDE_BASE,不是同一套东西,不能直接顶替。/obj/armours/、/obj/clothes/(复数,内容目录)完全不存在 (原始raw/里也没有;/obj/armour.c//obj/clothing.c这两个 基类文件本身是存在的)。/obj/weapons/、/obj/scabbards/等姊 妹目录反而都在,说明这份第三方重打包的内容裁剪并不均匀。/twiki/data/完全不存在:创作者协作 wiki 子系统的数据目 录,obj/handlers/twiki.lpc的find_changed_pages()会在每次 开机do_check_changes()时报一次*Bad argument 1 to explode()(read_file()读不存在的目录返回 0)。被 preload 的 catch 兜 住,纯创作者向工具,不影响玩家侧。secure/cmds/creator/compile.lpc里generate_source()未定 义:这个文件自己的注释就写着"compile command, trial out by Turrican for a commands daemon"——看起来是原始档案里就没写完的实 验性代码,两个分支(__RUNTIME_LOADING__开/关)都调用同一个从 未定义过的函数。已确认这不影响管理员 ACL 授权本身(详见上一节), 未做修复(不清楚generate_source()原本该对接到哪个真实的动态 编译机制,贸然补一个容易变成编造内容)。- MySQL 数据库不可用:这份第三方 bundle 有几处功能依赖 MySQL (
obj/handlers/map.lpc的 NPC 走路寻路会调用db_exec(),net/daemon/mysql.lpc,bin/mysql_handler等)。本 环境没有装 MySQL/MariaDB,db_exec()会抛Can't connect to local MySQL server...。这是本项目里已经反复出 现过的已知环境限制(nitan170911等 lib 的 NOTES.md 也记录过同 款问题),不在本次转换范围内解决。实测这个错误只发生在 NPC 自主 走路寻路时(被 catch 兜住,不影响任何一个真人玩家的核心指令), 没有观察到它阻塞注册/look/score/quit核心验证路径。 - 部分
/save/子路径缺少专属 ACL 授权:secure/master.o的permissionsmapping 对/save只有笼统的(["Room":3,"all":8,])(8=LOCK_MASK,对未被更具体规则覆盖 的子路径相当于"默认拒绝"),/save/handlers/error_handler.o、/save/inv_check.o这类路径没有更具体的授权规则,导致error_handler.lpc/inv_check.lpc的restore_object()在create()里报read permission denied(被unguarded()/apply_unguarded()的 catch 兜住,只在log/catch、log/runtime留痕,不影响任何一次实测的注册/登录/核心指令)。真实原因大概率 是这份重打包档案的secure/master.o存档数据本身就不完整(对照 下面 board 备份目录缺失的情况,怀疑是原站点部署时这些精细授权规 则是在运行中动态积累的,而不是随源码一起分发的)。未做修复,因 为这需要判断"这个具体路径本该授权给哪个 euid",属于内容/部署配 置层面的猜测,不属于驱动兼容性 bug。 /save/board_backups/目录缺失:board_handler.lpc定期把save/boards.o.gz备份到这个目录,目录不存在导致Error copying file ... No such file or directory,纯日志噪音, 不影响留言板本身的读写。
验证记录
lpcc_check.sh未使用(主库规模大、含大量非.lpc数据/资源文 件,参照 AGENTS.md §10.4"开机 + 交互测试就是充分验证门槛"的惯 例,直接以真实驱动开机+多轮交互验证为准)。- 真实驱动开机日志:0 条编译
error:,只有少量Unused local variable/Number of arguments...disagrees一类无害警告。 - 完整走过两次真实注册流程(
fluffos和wombletest,含 30 秒服 务端强制等待的条款确认环节),每次都在debug.log全程干净的前 提下进入真实游戏世界(著名的 Discworld 圆形大厅),look(含由 游戏自动触发的首次look)、score、quit均产出正确输出;用 已有存档验证过一次重新登录(restore 路径),退出流程干净("Do come again!" + 服务端主动断开)。 - 管理员账号
fluffos已验证:注册成功、重新登录后可见巫师专属横 幅、执行 wizard 级重编译指令compile时通过了 ACL 检查(卡在无 关的generate_source()缺失上,见上文)。
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.lpc 的 request_item() 在找不到道具时返回
0(有文档、有先例:早前转档时 walk_directory() 就因为同一个
"目录缺失返回 0 而不是空数组"的根因加过防御性 guard),但代码库里
至少 5 处 ARMOURY->request_item(...)->move(...) 调用链完全没有判
空,直接对返回值做 ->move()。 由于这份 "distribution lib" archive
本身缺失 /obj/clothes/、/obj/armours/ 两个内容目录(转档时已
记录的已知缺口),任何请求这两类道具的调用都会返回 0,0->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/liaison/NEWBIE/dummy.lpc(训练假人,"dirty rags")—— live 复现并验证修复:修复前one/two/three进训练间必崩,修复后干净重进(log/runtime全程无新增崩溃)。d/liaison/NEWBIE/trainer.lpc("Greg",新手战斗训练室 NPC,"leather jerkin"/"leather breeches"/"hard leather boots")—— 同一间训练室的教练 NPC,同一根因,未单独 live 复现(Greg 是 preload 期创建的,崩溃只在开机日志里,不在交互路径上现场触发),按同款 guard 一并修复。obj/monster/godmother/magrat.lpc、obj/monster/godmother/granny.lpc(地图上真实的"迷路仙女教母"NPC,"startling green dress"/"spiderweb shawl"/"pumps"、"hobnailed boots"/"antique black dress"/"witchs pointy hat"/"black witches cloak")—— live 复现:这两个 NPC 在本轮测试期间被游戏世界自身的某个触发点(不是我直接操作)真实创建过一次,log/runtime记录了它们各自的崩溃(见下一条,这两个是另一个独立根因,同一调用链形状,一并修的时候顺手发现的)。d/dist/pumpkin/rabbit/print_shop_office.lpc("Mr. Goatberger"商店 NPC,"white linen tunic"/"green pants")—— 未 live 复现(该 NPC 由reset()定期生成,不在本轮实测路径上),同款 guard 一并修复。
刻意没有动的同款代码: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.lpc 的 add_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.lpc 和 d/dist/pumpkin/rabbit/
print_shop_office.lpc 单独编译都直接报错;修复后两个都干净通过
(无 warning 之外的输出)。全库 grep 过其余全部 nosave/static
两个保留字被当裸参数名用的情况,只有这一处命中。已新增 AGENTS.md
§7.148 记录这个模式。
检查过、确认干净的标准 bug 模式(§10.7 清单里列出的横切模式)
- §7.139(
interactive catch tell配置项):Discworld 的颜色渲 染完全不依赖驱动的catch_tell()/receive_message()apply——整 个代码库里从未定义过catch_tell(),玩家可见文本的颜色标签 (%^TAG%^)是在 mudlib 自己的输出管线里显式调用驱动原生 efunterminal_colour()完成的(global/events.lpc的fix_string(),secure/simul_efun/strip_colours.lpc),再经由efun::tell_object()直接发送,跟这个驱动 runtime 配置里的interactive catch tell开关(config.fluffos里确认没设置,默认 0/关闭)完全无关。这个 开关只影响"驱动是否把catch_tellapply 当拦截点调用",而 Discworld 自己从没注册过这个 apply,所以配置项开不开都没有实际影 响——不是这个 bug 类的实例。 - §7.131(
find_living/find_player需要set_living_name()):global/player.lpc(两处,登录时和复活时)、obj/monster.lpc都 正确调用了set_living_name(),不受影响。 - §7.133(
net_deadapply 未定义):global/player.lpc、secure/login.lpc都定义了真正的net_dead(),这个驱动本身也 确实是按"直接调用 player 对象的net_dead()apply"设计的(不是remove_interactive(ob,linkdied)这种 master-level apply),双方 匹配,不受影响。 - §7.130(非交互后仍无条件调用
query_idle()):global/player.lpc的heart_beat()和secure/login.lpc的time_out()都先if(!interactive(...))分流,只在真交互对象上才调用query_idle(),写法正确,不受影响。 - §7.132(
map()遍历 mapping 时用错参数):抓了全库所有map(某变量, (: ... :))且该变量看起来像 mapping 的调用点逐一核 对声明类型,命中的全部是string*/int*/class数组,没有一处 是真的 mapping,不受影响。 - §7.122(class 自动重载在断连重连时复制道具):
compute_autoload_array/destroy_autoload_obj/load_autoload_obj这一整套 TMI-2 系亲缘 机制在这份代码库里根本不存在(Discworld 是完全不同的谱系),不 适用。 - §7.134(累加数组字段没有初始化):
std/room/basic_room.lpc的hidden_objects/_exits/aliases等全部数组字段都在create()里正确初始化为({}),不受影响。 - §7.126(.o 存档里残留
.c后缀的坐标出口路径)、§7.123(裸IDENT = (...))、§7.137(command("$verb"))、§7.140 (valid_read把 include 注入算到当前玩家头上):分别 grep 全 库,零命中或(§7.140)已被数小时的真实多角色游玩隐式覆盖验证 (每个新角色第一次触发的懒编译贯穿了几乎整个新手区,从未报过Cannot #include),判定不适用/干净。 - §7.112(NPC
init()无条件排call_out链):obj/monster.lpc唯一的init()只调用幂等的set_heart_beat(1)和条件触发的start_attack(),没有直接排call_out链,不匹配这个模式的形状。 - §7.141(
replace_program()折叠在开机后 ~5 分钟内让闭包创建崩 溃)——认真复现但未能触发,记录为观察项,不作代码改动:std/room/basic_room.lpc/basic_room_new.lpc的create()确 实有跟 dsI 那条一模一样的形状(replaceable()+sizeof(inherit_list())==1判断后call_out(delay 0)里调用replace_program()),而且calc_long_exit()(几乎每个房间look时都会算一次)在 3 个以上 出口时确实会创建一个真正的闭包 (map(words, (: mxp_tag("Exit",$1,1) :))) 且不区分客户端是否有 MXP——形状上完全符合。用真实的 trustee 管理员账号(fluffos,secure/master.o的positions里已有)在开机后 85~253 秒内(严 格早于这个驱动backend_register_tick_events()注册的、从开机起 每 5 分钟一次的replace_programs()扫描)连续对两个从未被访问过 的、真正满足折叠条件的房间(/d/dist/pumpkin/squash/squash5,4 个出口,经CITYROOM→outside→basic_room继承链;/d/dist/pumpkin/rabbit/print_shop_foyer,3 个出口,直接继承basic_room)做了 3 次独立尝试,每次都干净渲染出出口列表,log/runtime/log/debug.log全程零 "cannot bind a functional to an object with a pending replace_program()" 记录。没有进一步深挖 这个驱动为什么没触发(可能是call_out(fn,0)在这个版本的 backend 循环里比理论分析更快被处理,也可能是某个未追踪到的差 异)——按项目"不是每个模式相似的代码形状都真的在每个驱动/lib 组 合上复现"的既定原则,如实记录为"检查过、代码形状匹配但未能 live 复现",不做任何代码改动。
新观察:这是一份"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:199 的 set_level() 内部对它
做 call_other() 直接报 *call_other() couldn't find object
'/std/guilds/witch'(live 复现,log/runtime 有记录)。同样属于
"distribution lib 没有公会内容"这一句话能完全解释的已知限制,不修。
另外,d/liaison/NEWBIE/foyer.lpc 的 guilds 出口本身也会崩
(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,直接修复)
save/players/f/目录完全不存在:save/players/整棵树都是 git 未跟踪的运行时数据(.gitignore排除),这个沙盒环境里从来 没有出现过名字以f开头的玩家,导致这个字母桶目录压根没被创建 过。管理员账号fluffos第一次尝试注册时,断线时触发的net_dead()→save_me()(这条路径不受 30 分钟新手不存档规则限 制,是无条件保存)报*Could not open /save/players/f/fluffos.o.gz.tmp for a save.(log/catch),角色数据丢失,下次连接查无此人。直 接mkdir -p save/players/f(这个目录本身不在 git 跟踪范围内, 这个改动不会出现在任何 diff 里,纯粹是让这个沙盒环境的后续测试/ session 不再撞到同一个坑)。save/garbage.o的自愈式自动重启节流被这个 session 自己的反复 重启"作死"到了极限值:obj/handlers/garbage.lpc有一套"距离 上次重启是不是很快又崩了"的自调节机制(max_time初始 219600 秒/61 小时,每次create()时如果crash标记是真就打 5/6 折,只有在它自己发起的定时重启完整走完 9 分钟等待后才会清零crash标记、下次开机才会缓慢回涨,涨速只有跌速的 1/6)。这个项 目本身的开发/测试流程会大量直接kill+重启驱动进程(不经过check_reboot()自己触发的那条"优雅重启"路径),每一次都被这个机 制误判成"上次崩溃了",max_time单调下跌。到本轮测试开始时已经 跌到了 15 秒——开机后 heartbeat 走到第一次check_reboot()(create()里call_out("housekeeping",300)+housekeeping()里call_out("check_reboot",10),即开机后 ~310 秒)时,uptime() > max_time(15)恒真,直接触发"/obj/shut"->shut(10),10 分钟倒计时期间非管理员一律无法登录 (secure/login.lpc的find_object("/obj/shut")检查)。这不 是一个会在真实部署环境复现的 mudlib bug(真实部署不会像这个项目 的迭代式调试流程一样在几天内kill+重启驱动几十次),纯粹是这个 沙盒环境自己测试方式的副作用,按项目一贯做法(管理员账号也是直接 编辑存档种出来的)直接把save/garbage.o重置为健康初始值 (max_time 219600、crash 0、limit 1000),重启后验证正常 (非管理员账号顺利登录,没有再触发"too close to shut-down")。** 这套"崩溃惩罚,只有走完自己那条优雅重启全流程才能恢复"的不对称自 调节设计本身,在这个项目"驱动被频繁直接 kill/重启"的日常工作流下 会反复被打到最低值——如果以后其它 session 又撞到同样的"新角色注 册后立刻收到 too close to shut-down",直接检查/重置save/garbage.o而不用怀疑是新 bug。
已验证的核心流程(本轮 round-two 清单)
- 注册:
Roundtwo,含真实的 ~30 秒条款确认暂停,完整走到落地 新手大厅(d/liaison/NEWBIE/foyer.lpc),look/score/i每 次状态变化后都产出正确输出。 - 移动:foyer ↔ combat(新手战斗训练室)↔ commerce(新手商店) 多次往返,均正常。
- safe-sparring 机制:找到了(
d/liaison/NEWBIE/combat.lpc+ "Greg" 教练 NPC + 训练假人,标牌明确写着 "say 'can I practise please'"),bug 修复前one/two/three三个训练间入口必崩,修 复后干净可进入(Greg 后续对话触发条件跟具体措辞有关,受限于时间 预算没有把整套"打假人"流程走完,但核心崩溃点已修复并验证)。 - 公会/技能习得:从设计上不可达(见上文"distribution lib 裁剪"一节),如实记录为未测试而非"测试失败"。
- quit + debug.log 检查 + 隔一段真实时间重连:
quit后log/debug.log全程干净;等待若干分钟真实时间后用同一账号密码 重新登录,成功恢复到断线前的房间(战斗训练室),角色年龄 ("X minutes and Y seconds old")、登录次数计数、经验值、随身物 品("a fruitbat flavoured badge")全部正确延续,验证通过。 - 商店/经济:进入了新手商店(
commerce出口),房间描述与提示 正常;list命令测试未能在预算时间内确认真实商品列表输出(可能 是这个 driver 上 shop soul 命令的排队显示时序问题,不是本轮方法论 重点,未继续深挖,如实标注为未完全验证)。2026-09-04 已补完,见下 一节「商店切片」。 - 死亡/复活:未在本轮预算时间内触及,如实说明未验证。
管理员账号重新播种
上一次转档 session 种下的 fluffos 管理员角色存档本身没有随仓库提
交(save/players/ 整棵树不在 git 跟踪范围内),这次沙盒环境里已
经找不到了(secure/master.o 的 positions trustee 记录本身还在,
只是玩家存档丢了)。用同样的账号名 fluffos/密码 Mud@2026 走完
整套注册流程,真实挂机 30+ 分钟(满足 global/player.lpc 的
MIN_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))。本轮不再重挖
那一处。
端口 40206。global/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 / Play2026x。commerce 立刻
打出商店房间。同一会话只发一条 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 公会 内容未随包)。
2026-10-04 — #pragma no_warnings removed
std/effects/basic.lpc carried a mid-file #pragma no_warnings (before query_attrs()); it hid nothing: the file
compiles without a diagnostic once the line is gone. The same file, byte for byte, sits in skylib, discworld,
dw_fluffos_v1 and dw_fluffos_v2.
深度功能测试(§10.7,2026-10-04)— compile warnings: 248 -> 47
scripts/lpc_warnings.py discworld: 1699 files compile, 316 do not; 647 distinct diagnostics outside the 45 creator-help
examples under d/learning/help_topics/error_messages/ (248 warnings, 399 errors). Now 446: the same 399 errors
(files written for another lib: Undefined variable, Cannot #include, the player-shop office_code/*.lpc fragments,
www/; none touched) and 47 warnings. Those 47 are 33 in std/living/living.eff_shad.lpc (revision 1.72 of living.lpc,
kept beside it and inherited by nothing; it adds inherit "/std/living/effects" to a container that has it already) and
14 in files that do not compile (office_code fragments, net/daemon/nhttp.lpc, std/environ/weather.lpc,
secure/simul_efun/add_action.lpc). The creator-help examples show one compiler message each; their warnings are what
they are for, so lpc_warnings.py skips that directory (its first pass here emptied five of them: restored).
128 files for the 248 warnings (unused locals 95 -> 5, override arity 72 -> 0, inherited from both 38 -> 30, redeclarations 27 -> 4,
the 7 expressions without effect stay, eight others fixed):
varargson the override where the base isvarargs(init_dynamic_argof the furniture and containers,query_lookof the seven topography areas,test_remove,can_enter_from,event_death, ...).- Eight winners named (
F() { return Q::F(); }after the inherits; the driver already used W):query_multiple_short()insecure/simul_efun.lpc(frommodified_efuns),dest_me()in the four terrain-map bases (fromterrain_room) and instd/room/room_save.lpc,query_keep_room_loaded()there. Forroom_save.lpcW is theinherit/room_saveside, whosedest_me()only flushes pending saves; the#ifdef DONT_USEblock above it holds the combined version the authors turned off, and it stays off. - Names that met in one object, renamed on the leaf side with the uses following:
artifact.lpctype->artifact_type(rings, wands and staffs set it throughset_artifact_type()only),camera.lpccolour->film_colour(query_colour()ismixedover the base'sstring; the autoload key stays"colour"), pumpkindog.lpccolour->dog_colour,simple_disease.lpcshadow_ob->disease_shadow_ob,guard_duty.lpcid->call_out_handle,pickler.lpc_revenue->_pickler_revenue,bank_credit_note.lpc_id->_note_id,store_close.lpc_exits->_close_exits,club_discuss.lpc_discussion_items->_discuss_items. No object in those chains callssave_object(); they persist through autoload mappings with fixed keys. - The four socket daemons (
http,imap4,pop3,ftp_auth) each declared a secondSocketsnext tonet/inherit/server.lpc'sprivate nosave mapping Sockets:HttpSockets,ImapSockets,PopSockets,FtpSockets.pop3.lpcandimap4.lpcsave and restore themselves (net/save/pop3.o,imap4.o), and their copy was *not*nosave: the shippedimap4.ocarries a stale session table (a "ceres" session on fd 9) that every boot restored. The renamed tables arenosave, so nothing from an earlier boot comes back; the oldSocketskey in an existing.ois ignored. - Locals that only an inactive branch reads are declared inside it (
USE_RAMDISKinshop_base.lpctwice andcraft_shop.lpc,DISABLEDinread_desc.lpc,#ifdef 0inmtf2.lpcandquests.lpc,__DISTRIBUTION_LIB__inscore.lpc,compile.lpc,board_handler.lpc); the rest were deleted. - Single ones:
add_material()ofcmr_handlerisvarargs mixedover the base'svoid,dividor_room.lpcquery_main_room()ismixed,club_badge.lpcprototypes agree withclub_insignia,mtf.lpcreturn;->return 0;,newspaper.lpc#undef SAVE_DIRbefore its#define,parcel.lpc's commented-outget_dir("/save/parcels/*")has no/*inside the comment, andterrain_room.lpclost an emptyelse if (...) { }.
Checked on pristine trees with the same driver, HEAD against the working tree: boot and login of the seeded admin look
the same, the HEAD console prints 21 compile warnings and the new one none, the 3 compile errors it prints are the same
(finderror_helper.lpc, CONFIG_DB_ERRORS_USER). A throwaway object under /obj/ that loads or clones 31 of the changed
things through the lib's own master -- rings, wand, staff, the camera with an autoload round trip, the dog, the disease
effects, the pickler's autoload keys, the credit note, the two closing shops, the club, cmr, pop3, imap4, ftp_auth and
parcel handlers, room_save, the four terrain bases, query_multiple_short(), the present, mtf2, shop_base,
craft_shop, read_desc -- answers identically in both trees, while HEAD's batch prints 55 warning lines and the new one
none. (exec from a telnet session returns nothing here: the game queues each command and the answers never arrive.)
scripts/lpc_audit_removed_locals.py lists every deleted declaration the function still reads: it found the four locals the
first pass deleted from inactive branches (mtf2.lpc, read_desc.lpc, shop_base.lpc, craft_shop.lpc; back in their
branch), and a read of the diff found a deleted object ob in the include fragment office_code/lists.lpc whose next line
reads it (the compiler's "unused" came from a scope an earlier error had confused; restored).
深度功能测试(§10.7,2026-10-04)— include/runtime_config.h was the MudOS-era one
The header numbered the integer settings from 15 where this driver numbers them from 256 (KB 06 §7.89), so
get_config(__MAX_BUFFER_SIZE__) answered "" and /net/daemon/http's create() died with Trying to put string in
int (line 133) every time it was loaded; the web daemon never came up. The header is the driver's own copy now. The
four settings http.lpc reads (__MAX_BUFFER_SIZE__, __MAX_STRING_LENGTH__, __MAX_READ_FILE_SIZE__,
__MAX_BYTE_TRANSFER__) are the only symbols the lib takes from it; load_object("/net/daemon/http") answers an object
now and the rest of the load-and-clone check is unchanged.
深度功能测试(§10.7,2026-10-05)— 主控 domain_file("/")
驱动的 mudlib_stats 包在主控载入时调用一次 domain_file("/"),要求返回字符串。本库 secure/master/creator_file.lpc 的 domain_file() 在 creator_file() 没有答案时返回 0,"/" 正是这种情况,所以每次启动都打印 domain_file() in the master file does not work, using bb_ui as fallback 一行,网页版每位访客的终端里都看得到(author_file(<主控文件>) 本来就返回 root uid,不报警)。现在 domain_file() 对 "/" 直接返回 get_bb_uid(),正是驱动缺省时用的值。其他路径不变:创作者的 ls 和 ftpd 直接调用 master()->domain_file(),返回 0 时显示 "Root",所以没有把 0 一律改成字符串(KB 06 §7.222)。用 lpcc config.fluffos /nonexistent.lpc 前后对比,主控载入段只少了这一行。
深度功能测试(§10.7,2026-10-05)— 压缩存档改为明文;board_backups 目录
网页版的 WASM 驱动没有 zlib,restore_object() 只读 <名>.o,打包脚本也排除 *.gz;本库以 .o.gz 存的种子存档(save/players/f/fluffos.o.gz 等 7 个:管理员、bastards、boards、hist_handler、armoury、intermud、rcs_handler)在网页版上全部缺失:文档里的管理员 fluffos / Mud@2026 在网页版上不存在,登录时打印 Failed to restore bastards.。现已 gzip -d 成内容逐字节相同的明文 .o(KB 01)。本机驱动在没有 .o.gz 时读 .o,之后自己的压缩存档会替换它。另外 board handler 启动时把 boards 备份到 /save/board_backups/,目录不存在,每次报 Error copying file;加了 save/board_backups/.gitkeep。验证:本机登录 fluffos / Mud@2026 成功,无 bastards / 复制错误;WASM(解开的 site zip + 明文存档)同一密码登录成功。
2026-10-05 — CONFIG_DB_ERRORS_USER 未定义
obj/handlers/finderror_helper.lpc(finderrors 创作者命令与各领域错误追踪的助手)和 www/secure/creator/bug_top.lpc 用了 CONFIG_DB_ERRORS_USER,公开发布版的 include/config.h 删掉了这个定义,两个文件都编译失败。在全新检出(及网页版)里启动时会载入它,打印 3 条编译错误。config.h 补上 #define CONFIG_DB_ERRORS_USER CONFIG_DB_USER;全新树启动 0 错误 0 警告。
深度功能测试(§10.7,2026-10-08)— .lpc 后缀的定宽切片
.c 改名 .lpc 时只改了字符串没改切片宽度:f[<2..] == ".lpc" 拿两个字符比四个字符,永远不成立(!= 永远成立),按后缀筛文件、去后缀的代码因此从不运行或截成 x.l(KB 06 §7.238)。scripts/lpc_lpc_suffix_slice.py --apply 改了 11 处比较、5 处去后缀(如 www/footer.lpc);改动的文件 HEAD 与工作树分别加载(新进程),无回退。
- 2026-10-10:编译警告第二轮(按驱动报告逐条修):按位 & 改逻辑运算 0 处,返回类型/原型 0 处,NPC+F_UNIQUE 的 init() 0 个,带初始化的未用局部变量 1 个,只在 #if 分支用的声明移进分支 0 个文件;改动 1 个文件,HEAD 与工作树各载入一次无回退(1 objects; HEAD PASS 0, tree PASS 0; regress 0; newly loading 0),这些文件的警告 1 -> 1。
Compile warnings (2026-10-13)
scripts/toolkit_fix.sh round: secure/simul_efun.lpc inherited /secure/simul_efun/multiple_short directly although modified_efuns (inherited just above) already brings it, so every polyfill function was compiled twice; the direct line is gone and the comment says where the polyfill comes from. std/environ/weather.lpc's untyped move(arg) is now mixed move(arg). Both load the same at HEAD and in the tree (regress 0).
Left as is: std/living/living.eff_shad.lpc (33 warnings), an older variant of living.lpc that nothing inherits or loads. It inherits /std/living/effects (a one-line inherit "/std/basic/effects") beside /std/container, which brings the same effects code, and calls effects::create(), effects::add_effect() and effects::stats() by that scope; the live living.lpc dropped the second inherit and reaches effects through container::create(). Removing the duplicate means rewriting how this unused file is put together, so it stays as archived.
- 2026-10-14: FluffOS 970d9f09 warns
Float value truncated to int(#1426). 13 sites in 10 files: assignments, initializers and returns now spell the truncationto_int(...)(same value);x op= <float>converted the float before the operation (zeroingxfor a factor below 1) and is nowx = to_int(x op ...):/std/shadows/misc/water.lpc(buoyancy *=). Changed objects loaded before and after in fresh drivers, no regressions (scripts/lpc_fix_float_trunc.py, KB 04 §6.10).
- 2026-10-14: 1 files did not compile with
Type of returned value doesn't match function return type ( int vs void ): a function declaredvoid(or with no type) returns a value, which the old driver passed to the caller. Functions whose value a caller reads (add_action handlers, commands, helpers) now carry the type they return:do_seek(). HEAD vs tree in fresh drivers: 1 objects, 1 newly loading, 0 regressions (scripts/lpc_fix_void_return.py, KB 04 §6.3).
Base classes the lib did not ship, restored from sibling libs (2026-10-18, KB 04 6.15)
These base classes are not in the lib, so every object that inherits them (rooms, NPCs, items) failed to load (inherit XXX; with no macro, or inherit "/std/..."; with no file). A sibling lib carries the file and shares many files byte for byte with this one, so the file is copied as it stands: /std/effects/healing/basic_cure <- skylib, 1 object(s) inherit it, sample 1/1 load. Each base was loaded alone and a sample of its users after it; a base that did not load, or none of whose users did, was taken back (scripts/lpc_restore_bases.py).
Macros the global header lost (2026-10-19, KB 04 6.16)
/include/all_inc.h did not define C_TEMP, C_CLOUD, C_RAIN; the objects that use them (4 on the list) stopped at Undefined variable/Undefined function, and since the compiler stops at the first undefined name the errors and warnings behind it stayed hidden. The sibling libs of the same lineage carry these lines in their headers: the copy of the sibling with the most files in common was added after the last #define of /include/all_inc.h; every path in a macro body exists in this lib. the listed objects have other errors as well and still do not load. (scripts/lpc_restore_defines.py)
The unused living.eff_shad.lpc variant, finished (2026-10-19, KB 06 7.240)
std/living/living.eff_shad.lpc(the 33 warnings the earlier rounds left as archived) carried/std/living/effectsbeside/std/container, which already brings/std/basic/effectsthrough/std/object. The variant now does what the liveliving.lpcdoes: the redundantinherit, theeffects::create()call and theadd_effect()wrapper are gone, andstats()no longer appendseffects::stats()a second time (object.lpc'sstats()already ends with it, so the variant listed the effects twice). It loaded with 33 warnings at HEAD and loads with none in the tree (regress 0); nothing inherits it.
Old-driver type slack: missing arguments, untyped parameters, array initializers, class variables (2026-10-19, KB 04 6.19, 6.20)
MudOS filled the missing arguments of a call with 0, took an untyped parameter as mixed, never compared a declared type with its initializer and let a variable be named class; this driver refuses all of it at compile time and the whole object fails. 4 file(s) fixed: /d/learning/examples/terrain_map/basic/field_handler.lpc, /obj/fungi/glow_toadstool.lpc, /std/environ/weather.lpc, /std/npc/taliswoman.lpc (the repair rounds of scripts/lpc_round_files.py: zeros written out at the call, mixed for untyped parameters, string X = ({...}) given its star, void functions returning values, and class variables renamed pick_class). Loaded at HEAD and in the tree, per file: 2 of them failed before and load now, none regressed.
x != "a" || x != "b" and x == "a" && x == "b": comparison chains of one value that can never differ (2026-10-20, KB 06 7.282)
if (arg != "wan" || arg != "pill") return notify_fail(...) is true whatever arg is, dir == "south" && dir == "north" is never true: the guard under the test never opens, or never closes. The author meant "neither of these" (&&) or "either of these" (||). != chains joined by || became && (only where the statement under the test is a return, a refusal or a guard clause), == chains joined by && became || (scripts/lpc_fix_cmp_chain.py). What it was: push lever always answered "You must push an existing lever." (str == "levers" && str == "lever"). Changed: /std/environ/weather_room.lpc (1).
member_array('/', str) became strsrch(str, "/"): the character-literal member_array is a byte search (2026-10-20, KB 06 7.283)
'/' in member_array('/', str) is an int, so the driver searches the UTF-8 bytes and answers a byte offset (a third argument is a byte offset too). FluffOS 923cdbc0 warns once per call site (Warning: member_array(int, string) searches BYTES) when the string holds non-ASCII text before the match, and str[0..idx-1] counts characters, so a non-ASCII string was cut in the wrong place. Now strsrch(str, "/") (-1 when absent, a character index; scripts/lpc_fix_member_array_char.py, the JSON encoders and a few other sites by hand). Changed: /obj/misc/housing_creator.lpc (1).
if (x = CONST): a comparison written as an assignment (2026-10-20, KB 06 7.290)
if (arg = "button" || arg = "pill"), if (!arg || arg = "") return 0;, else if (check = 0 && tempB != ""): = was typed for ==, so the condition is the constant: the branch is always taken for a non-zero number or any string (even "") and never for 0, and the variable is overwritten; the driver does not warn. Every such = became == (scripts/lpc_fix_cond_assign.py, 1 terms). Changed: /cmds/theft_base.lpc (1).
A login password over 255 bytes: crypt() raises (2026-10-20, KB 06 7.294)
The driver's crypt() raises (Error in crypt(), check your salt) for a key over 255 bytes, where the old driver hashed any length. A password of 256 bytes or more typed at the login prompt threw an error in the password check and left the connection at a bare > prompt, so the login never completed (the old-password test of password the same); found by the sweep shape of 300 x. /secure/simul_efun.lpc now defines a simulated efun crypt(key, salt) of the same name: only an over-long key is cut (to 255 characters, then character by character until sizeof(to_buffer(key)) <= 255: the limit is in bytes, a CJK character takes three) before it is handed to efun::crypt(); every ordinary password hashes as before and the saved hashes are untouched (scripts/lpc_add_sefun.py crypt).
time did not compile: config.h came after the __DISTRIBUTION_LIB__ test (2026-10-20, KB 06 7.312)
cmds/player/time.lpc had #ifndef __DISTRIBUTION_LIB__ #include <special_day.h> #endif before #include <config.h>, the header that defines __DISTRIBUTION_LIB__, so the test was always true and the file included special_day.h, which the distribution lib does not ship: the time command never compiled. #include <config.h> now stands before the test.
Heart beat every two seconds: the command queue never refilled (2026-10-20, KB 06 7.314)
psoul.lpc query_time_left() refills the command budget with time_left += ((time() - tl_updated) / 2) * ROUND_TIME and then sets tl_updated = time(); the player's heart_beat() calls it every beat. The MudOS Discworld beat every two seconds. The driver's default heartbeat interval msec is 1000, so time() - tl_updated is 1, the integer division is 0 on almost every call, and time_left stays at -20: after the first command every other one answered Queued command: ... and nothing ran for minutes (restart and stop did not help; a reconnect to a net-dead body was the same). config.fluffos now has heartbeat interval msec : 2000; live, i, score, date, who and look answer at once in a first visit and after a reconnect.
Player saves need every letter directory (2026-10-20, KB 06 7.315)
save_object() makes no directories, and the Discworld mudlib has no on-demand mkdir for save/players/<letter>/, save/newsrc, save/mail or banish: they existed in the archive only for the letters somebody had played with (only f), so a character whose name starts with another letter was never written (Could not open /save/players/s/NAME.o.gz.tmp for a save in log/catch at the quit and at every autosave; the login survey said in, saved 0). scripts/lpc_keep_letters.py --apply completed the four buckets in scripts/wasm_keep_dirs.txt (the site loader and pristine_tree.sh make them); live, save/players/s/sweepzq.o.gz is written at the quit. The mortal sweep also needs letters-only names (sweepab) and the N entry of the login menu.
Timed reboot: the mud asked everybody to leave 25 minutes after the boot (2026-10-20, KB 06 7.319)
obj/handlers/garbage.lpc check_reboot() shuts the mud down when uptime() > max_time; max_time is saved in save/garbage.o and shrinks by a sixth at every boot that follows a crash. The committed file had been shrunk by earlier boots to 14255 s, so log/REBOOT said Shutdown requested automatically about four hours after the boot, Offler announced the reboot, logins were refused (It is too close to the shut-down) and the driver exited: on the site nothing starts the mud again. NO_TIMED_REBOOT is now defined (the original ran 61 hours between reboots; the museum has no supervisor). Do not commit the churn of save/garbage.o.
Creator commands that did not compile: actions_defined, generate_source, mud_list, lpc_info, function_profile, __OPTIMIZATION__ (2026-10-20, KB 06 7.323)
The creator sweep as the seeded admin (play_sweep_lib.py discworld --account fluffos:Mud@2026 --dirs creator,lord,playtester --burst 1.3) printed compile errors at the first verb of 13 commands. actions_defined() is called by comment (and the eight objects that inherit cmds/report_base.lpc), which and loc_alcmd; the lib's stub for it sits in secure/simul_efun/add_action.lpc, a file simul_efun.lpc inherits only #if !efun_defined(add_action): this driver has add_action, so the stub was skipped with the file. compile needs generate_source() (the original driver's LPC-to-C compiler), lpc_info needs lpc_info(), prof needs function_profile() (a build option this driver lacks), fry called mud_list() where its own first line says mud_name(), and mudinfo defined __OPTIMIZATION__ only inside its #ifndef __COMPILER__ guard (this driver defines the compiler macro and not the other). New secure/simul_efun/driver_gaps.lpc (inherited by simul_efun.lpc; each stub behind its own #if !efun_defined(x)): actions_defined() answering ({}) as the old stub did (varargs: loc_alcmd passes one argument), generate_source() raising LPC to C compilation is not available on this driver. (compile catches it and prints the message), lpc_info(), function_profile() (an empty profile); the stub is gone from add_action.lpc; fry uses mud_name(); mudinfo has its own #ifndef __OPTIMIZATION__. Gate: 464 objects (cmds, secure/cmds, secure/simul_efun) at HEAD and in the tree, regress 0, 13 newly load; live as the admin which look lists /cmds/living/l_ook and comment, compile, loc_alcmd print without a console line. Still not loading, and not bugs of this port: pstatus (the word broken stands before its #include: the authors disabled it), errors_base2 (MySQL), theft_base and sh_out (headers of areas the distribution lacks), sit (its create() asks this_player(), it loads when a player types it).
存档里的 .c 文件名改成 .lpc(2026-10-21,KB 03 §4.2 第 9 项)
存档里写着 "/d/city/npc/wei.c" 这样的文件名;extract 时源文件已经全部改成 .lpc,而驱动对带扩展名的路径只认字面,new() / load_object() / move() 都找不到它,调用方多半把失败当成「没有这个东西」悄悄跳过。本库 2 个存档共 7 处按 scripts/lpc_fix_save_dotc.py 改成 .lpc(只改 .lpc 存在而 .c 不存在的字符串;玩家存档、备份、留言板、巫师目录和文件索引缓存不动)。改动:/save/armoury.o(5 处)、/secure/master.o(2 处)(共 2 个存档)。
俱乐部控制室 do_transfer():失败分支销毁没赋值的 obj(2026-10-21,KB 06 §7.348)
do_transfer() 在 remove_money() 失败时执行 obj->dest_me();:这是从 do_withdrawl() 抄来的(那里先造了一笔钱 obj,失败时销毁它),do_transfer() 里什么也没造,object obj; 从未赋值。转账失败时抛错,而不是提示 "Unable to remove the money from the account."。去掉声明和那一句。改动:/std/room/inherit/club_control_room.lpc。
add_action() registrations of functions nobody defines (2026-10-21, KB 06 §7.349)
An add_action("fn", "verb") whose function is defined neither by the object, nor by its headers, nor by its parents, nor by any object derived from it (scripts/lpc_scan_callout_targets.py, descendants checked transitively, then function_exists() in a running driver, scripts/lpc_verify_handlers.py). The driver walks the actor's sentences from the newest one: when that sentence's function does not exist it raises *Function for verb 'drop' not found. and tries no other handler of the verb (an older dead sentence is passed over silently). Removed 5 registrations in 1 files (scripts/lpc_fix_dead_add_action.py), the verbs are left to their other handlers: /obj/roommak.lpc (do_add, do_map, do_modify, do_set, ...). Most frequent: do_add x1, do_map x1, do_modify x1, do_set x1, do_setup x1.
Combat probe with the seeded administrator (2026-10-22): 56 of the 87 /std/effects/... paths the lib names do not exist
scripts/play_combat_probe.py discworld --account fluffos:Mud@2026 --clone ... --pace 2.5 (Discworld queues one command per heart beat) fights the newbie area's womble: the screen shows *call_other() couldn't find object '/std/effects/ingested/tranquil_sleep'. The whole std/effects/ingested/ directory (drunk 16 references, cum_poison 16, poison 13, basic_healing 6, nausea 5, garlic_breath 4, tranquil_sleep 3, ...), poisons/ (spider_venom 5, poison_gas 3, ...), healing/antiseptic_soap, religious/* and fighting/combat are named by objects but not in the lib; add_effect() / add_eat_effect() of them raises. 31 of the 56 exist in skylib (a Discworld fork): its ingested/ effects load in this tree (basic_healing, blind_protect, calm, cold_cure, garlic_breath, int_bonus, knurd, strength_boost, poison_antidote, ...), the ones that matter most do not as they stand (path.h and the std/shadows/ingested/ shadows are missing here, and skylib's global/standard.h macros ENV / TP / TO / CAP are not defined by these libs). Not changed: a port of about 30 files, queued.
Missing std/effects files adopted from skylib (2026-10-22)
The section above counts 56 /std/effects/... paths that objects of this lib name and the lib does not ship: the womble of the newbie foyer (tranquil_sleep), every drink or herb with an eat effect (drunk, poison, cum_poison, basic_healing, nausea, ...), the scorpion's and the traps' venom (spider_venom, poison_gas), the group command (other/group), consider (it asks PLAYER_DRUNK_EFFECT), the swimming rooms (other/water_effect). libs/skylib is a Discworld fork whose effect core (std/basic/effects.lpc, std/effect_shadow.lpc, the add_effect() / beginning() / merge_effect() / restart() / end() / submit_ee() API, the effect timer's call convention, std/shadows/misc/effects.lpc) is the one of this lib, and it ships most of the missing files (many still carry the original Discworld RCS headers of Ceres, 1998). 51 new files are adopted from it (36 effects, 11 shadows, 4 path.h), all under work/std/; no existing file was edited. Every adopted file starts with // adopted from skylib 2026-10-22: <what was changed>.
Effects adopted unchanged (28): healing/antiseptic_soap, healing/simple_cure, ingested/{basic_charging, basic_healing, blind_protect, calm, cold_cure, cum_poison, garlic_breath, hallucination, int_bonus, knurd, poison, poison_antidote, strength_boost}, object/ward, attached/see_invisible, other/{alertness, dog_urinate, fish_effect, group, ink, meditate_effect, perfume, rubbish_smell, water_effect}, poisons/{constitution, poison_gas}. Shadows adopted unchanged (7): std/shadows/ingested/{blind_prot_shadow, drunk, hallucination, poison}, std/shadows/object/ward, std/shadows/other/fish_effect, std/shadows/attached/see_invisible. New path.h (copies of skylib's): std/effects/ingested/path.h, std/effects/poisons/path.h, std/shadows/ingested/path.h, std/shadows/other/path.h (the std/effects/{attached,object,other}/path.h already existed).
Ports (12 files), each one a token-level edit:
std/effects/ingested/drunk: skylib's global macrosENV(x),TP,INV(x),->HISwritten out asenvironment(x),this_player(),all_inventory(x),->query_possessive(); thetp = TP; set_this_player(person); ... set_this_player(tp);pair dropped (this driver is the nativeadd_actionbuild:set_this_playeris not an efun there,secure/simul_efun/add_action.lpckeeps a stub that raisesIllegal use of set_this_player., andperson->new_parser("hiccup")acts for the object itself, as in the lib's owndisease/common_cold,disease/general_hypothermiaandnpc/vampirebite_effect);things[i]->query_worn()(a function neither lib has: a worn item could be dropped) becamequery_worn_by().std/effects/ingested/nausea:ENV/TPwritten out, theset_this_playerpair dropped (same reason).std/effects/ingested/tranquil_sleepandstd/shadows/ingested/tranquil_sleep:ENV/CAP/TO/->HEwritten out,#include <living.h>forPASSED_OUT.std/effects/ingested/breath_fresh:end()acts on itsplayerargument instead ofthis_player()(an effect timer runs under whoever registered the call_out).std/effects/ingested/vial_healing,std/effects/object/oak_wand_healing: skylib's sefuncreatorp(player)becameplayer->query_creator().std/effects/other/powdered:extra_look()capitalises the pronoun (it printedhe is dusted with red.).std/effects/poisons/spider_venom: theHEALTH_H->monitor_points(player, 1)line (skylib's health monitor handler, undefined here) removed.std/shadows/other/group:ENV/TP/TO/PO/->HIMwritten out; skillgeneral.perceptionbecameother.perception(skylib renamed the skill tree: this lib has nogeneral.*, itswatchcommand andblind_prot_shadowuseother.perception).std/shadows/other/alertness: skillgeneral.perceptionbecameother.perception(otherwise the shadow never adds its bonus).std/shadows/other/meditate_shadow:#include <living.h>forPASSED_OUT.
Beyond the first list of 31 (dependencies and live data references, all with a donor in skylib): healing/simple_cure (SIMPLE_CURE of include/disease.h, inherited by antiseptic_soap), poisons/constitution (strength_boost.end() adds it: without it the boost ended in couldn't find object '/std/effects/poisons/constitution'), attached/see_invisible + its shadow (obj/amulets/cat's_head.arm), ingested/vial_healing (obj/food/meals/*.food), object/oak_wand_healing (obj/plants/yarrow.ob, thorny_burr.ob, angelica_root.ob).
Evidence.
- Before:
kill womblein the foyer printed*call_other() couldn't find object '/std/effects/ingested/tranquil_sleep'. Object: /global/lord#17 (/std/basic/effects.lpc) at line 668. - Load check on a pristine tree plus the files (
scripts/lpcc_eval.py discworld ... --at /obj/zzeval.lpc, a fresh driver each time):catch(load_object())of all 36 effects, thenquery_shadow_ob()of each loaded and that shadow loaded: no error and no warning (a deliberately warning-producing control file printed its warnings in the same run). Staged first (the donors as they stood:drunk,nausea,tranquil_sleep,spider_venom,antiseptic_soapand the shadowstranquil_sleep,other/group,other/meditate_shadowfailed withUndefined function ENV/Undefined variable 'TP'/'TO'/'PASSED_OUT'/'HEALTH_H'/Inherited file '/std/effects/healing/simple_cure' does not exist!and the missingpath.h). - Live, a fresh driver from the working tree (
scripts/play_boot.sh), the seeded administrator and a new mortal: the womble fight printsYou feel very sleepy.,You can't keep your eyes open anymore. You go to sleep.,You dream of chasing a pair of trousers through a field of flamingoes.,You wake up feeling refreshed.(effects melistsbody.sleep.tranquil (120), the look saysHe is fast asleep.). A new character takes the champagne bottle from the newbie bucket anddrink bottle:You drink a few ounces of fine champagne from the champagne bottle.,say hello there, sirprintshello therre, shirr(drunk shadow), two minutes later the real call_out timers have producedYou trip over and fall on your face.,You burp.,You hiccup.. Every effect added withadd_effect()and its event queue run to the end witheffect_timer(): drunk (also on an NPC, withknurdcounteracting it), knurd, nausea, poison, cum_poison, poison_antidote, poison_gas (amount 8), hallucination, tranquil_sleep, blind_protect, calm, garlic_breath + breath_fresh, cold_cure againstdisease/common_cold, int_bonus, strength_boost (and theconstitutionbacklash), vial_healing, basic_healing, basic_charging, alertness, dog_urinate, rubbish_smell, perfume, meditate_effect, fish_effect, powdered, see_invisible, antiseptic_soap againstdisease/flea_infestation, ward on an object (The zz test device is warded.); the scorpion race'spoison()intospider_venom(Seething pains radiate from the wounds inflicted upon you by the scorpion.); eating/obj/plants/yarrow.ob(You have been healed 12 hp.plus the cum_poison it carries) andhemlock.ob;group create/status/say/history/rename/leave(effect and shadow added and removed);consideron an NPC (it callsPLAYER_DRUNK_EFFECT->query_drunk_level());watch/watch stop;stare at lampon the lava lamp (meditate_effect); anobj/misc/ink_wellclone (it asksother/inkfor its coordinate); a scratch room inheriting/std/room/water_outside(water_effect:Your lungs feel quite heavy.). The lib's runtime error log held no entry from any of these files in any run (only the MySQL map-handler lines and the items under "Seen on the way"). Not exercised live: thewardshadow'smove()against a thief,blind_prot_shadowagainst a blinding effect (the lib has none), thefish_effectwater damage,see_invisiblewhile wearing the brooch (.armvirtual items did not clone directly in the scratch test).
Still missing after this (no donor in skylib or in any other lib of this repository; nothing was invented): fighting/combat (named by disease/rabies, room/albatross_attack, living/skills, obj/handlers/taskmaster), poisons/{angry, bloat, con_new, confuse, hp_new, smokebomb} (obj/state_change/base.hdr_dc.lpc and the heat / distill / press .dat files: the cooking and distilling poisons; skylib's poisons/hp is the old potion-space file, not hp_new), religious/{basic_ritual, blind, shield, theological_debate} (debate command, obj/handlers/philosophies, std/living/armour, std/shadows/misc/poker_shadow), magic/{invisibility, virtual_spell} (obj/rings/invisibility, include/virtual_spell.h), disease/{broken_leg, jellyfish_sting} (obj/misc/traps/leg_trap, std/races/jellyfish; skylib has a curses/broken_leg under another path), the faith/*, magic/{magic_circle, grabbing_hands, reduced_weight, witch_eye} and object/rubbery_weapon names that only secure/rcs_handler.o (an RCS file list) carries. Donor exists but only a backup (obj/reactions/base.hdr_dc.c.old) or a doc names them, so not adopted: external/fungus_cure, healing/cold_remedy, healing/weak_hp, ingested/{frog_repellent, potion_spellcheck, weak_poison_cure}, magic/magic_spell. ingested/query_drunk_level() does not exist in skylib's drunk (consider asks it for the considerer's drunkenness): the call returns 0, so the drunk-considerer lines of con_sider.lpc never show.
Fixed on the way (outside the effect files, same day):
std/races/spider.lpc:poison()calledtell_room(me->one_short() + " visciously sinks its fangs into " + ...)with the message as the first argument (the sefun reads it as a path:*Read access denied.), so a spider's bite raised beforeadd_effect(spider_venom)and a spider never envenomed. It is nowtell_room(environment(me), ..., ({ me, him }))like the scorpion race.scripts/lpcc_eval.pyon a pair of/obj/monsterclones in a room, HEAD and tree: HEADpoison()raises*Read access denied.andhimhas no effect; the tree returns clean andhimcarries the venom effect.std/effects/disease/hypothermia.lpcline 98 added/std/effects/wetness; the effect lives atstd/effects/other/wetness(asgeneral_hypothermiahas it): a dislodged lump of ice raisedcouldn't find object. Path corrected;objectp(load_object(...))is 0 for the old path and 1 for the new one.
Seen on the way, not fixed (outside the effect files):
- A scratch room inheriting
/std/room/water_outsideraised*cannot bind a functional to an object with a pending replace_program()fromstd/room/outside.lpc:318(clean_up_room) andstd/room/basic/water.lpc:1297(the swim exit); the lib ships no water room underd/, only the inheritables. get badgeas the administrator raised*call_other() couldn't find object '/secure/alist'fromobj/handlers/multiplayer.lpcline 324 (seen once, not checked for mortals).
clone_object() of a shadow the lib never shipped, split over two lines: the -> chain guarded (2026-10-22, KB 06 §7.73 addendum)
std/object.lpc init_dynamic_arg() restores a "special" item with clone_object( "/std/shadows/object/special_item" )->\n setup_shadow( this_object(), player );: the file is not in this archive (std/shadows/object/ has the other shadows), clone_object() of it raises for the object that carries the special dynamic argument. The statement is now { object new_ob = clone_object("/std/shadows/object/special_item"); if (new_ob) new_ob->setup_shadow( this_object(), player ); } (scripts/lpc_guard_carry_object.py --new). The earlier chain-guard pass never reached this lib: the --new regex took exponential time on discworld's source (identifier tokens could be split anywhere) and the run was abandoned; with a word boundary after each identifier the scan of the lib takes 2 seconds. Load check of the changed file, HEAD vs tree: loads in both (the statement sits in a branch that only runs for a saved special item).
log/ subdirectories nobody created: the logging write_file() / log_file() raises (2026-10-22, KB 06 §7.358)
The code of this lib writes logs into 4 subdirectories of log/: log/errors (net/intermud3/services/error.lpc), log/http (net/daemon/http.lpc), log/log/secure/playtesters (obj/handlers/library.lpc) .... Neither the archive, a git clone nor the site pack has them (no local boot wrote into them, so gen_keep_dirs.py never recorded them), and write_file() raises *Wrong permissions for opening file /log/dlog/money for append. "No such file or directory" for a missing directory: the function that logs stops on that line and what came after it (the reward, the flag, the next step) never runs. In the eval probe this lib's log_file() wrote nothing (the probe object has no write access and the sefun does not make the directory itself), so what a real caller gets was not measured. log/errors, log/http, log/log/secure/playtesters, log/secure are now in scripts/wasm_keep_dirs.txt (scripts/lpc_keep_log_dirs.py; the site and the pristine tree create the empty directories from it). Proof (scripts/lpcc_eval.py, an eval object under /adm/daemons or similar writes /log/zzdirprobe/..., fresh processes): the measured table in KB 06 §7.358.