info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
LIMA 社区仍在维护的参考 mudlib(源码: <https://github.com/limalib/lima>,官网 <https://www.limamudlib.dev/>)。这不是一款武侠/仙侠游戏——和本项目收录的其它中文 mudlib 不是同一个宗谱,是一个独立的、英文的、面向开发者的现代 LPC 框架,收录进本项目是作为 "当代 FluffOS mudlib 长什么样"的参照样本。
English
The maintained LIMA mudlib (limalib/lima, limamudlib.dev) -- an English-language, developer-facing framework built to demonstrate what a modern LPC mudlib looks like, quite unlike the classic Chinese wuxia lineages that make up most of this collection. Rather than registering one verb per add_action, Lima understands full natural-language sentences ('take the red potion from the box'), secures files and commands through a capability-based system of privileges/domains/protections instead of the traditional uid/root model, and gives every wizard a Unix-like shell (variables, pipes, globbing) rather than a fixed command table. New characters begin in the Grand Hall, a wizards' meeting room with polished floorboards and rough-hewn beams, flanked by a sulfurous northwest passage, a passage to the south that smells like something recently died, and an incongruous elevator door to the west; a glowing portal leads to the separate mortal starting area. Bundled demo content includes a nine-room 'Sandy Beach' pirate treasure-hunting puzzle (explicitly marked by its own author as not meant for a real, live mud) and optional contributed modules for bulletin boards, marriage, and in-room object creation. New characters are granted wizard powers automatically (AUTO_WIZ), the upstream project's own default for a framework meant to be explored and extended, not played as a finished game.
README
github.com/fluffos/lima 已于 2024-10-08 归档,不再是活跃上游。本馆按 submodule-patch 跟踪 limalib/lima:work/ 是该仓库的 submodule(mudlib 根目录是 work/lib,上游保持 .c),本馆 FluffOS 兼容在 patches/,管理员播种在 overlay/。真源码 bug 往 limalib/lima 提(本馆已合入的 #49 / #50 / #51 在上游 2026-09-03 HEAD ffed9c588ee5)。
架构说明
Lima 在几乎每个层面都和本项目里的经典 LPMud 系 mudlib 不同:
- 集中式自然语言命令解析,而不是逐个动词
add_action注册——能 理解take the red potion from the box这样的完整英语句子。 - 能力(capability)式安全系统(
secure/daemons/secure_d.lpc), 基于privileges/domains/protections而不是传统的 uid/root 模型。 - 巫师用的是类 Unix shell(
? for shell help>提示符),支持 变量、管道、globbing,而不是一张固定的巫师指令表。 AUTO_WIZ默认开启——任何新建角色都会立即获得巫师权限,这是 上游仓库自己的默认设置(Lima 本来就是给开发者当框架/演示用的), 不是本项目引入的开局漏洞。
详见 NOTES.md,里面有完整的转换记录、发现并修复的驱动兼容性 bug、
以及为什么这个 lib 需要一个专门编译的驱动。
特殊要求:需要专用驱动
这个 lib 不能用本项目其它 ~240 个 lib 共用的默认驱动
(~/src/fluffos/build-debug)启动——Lima 自带的
secure/check_config.c 会在启动时检查驱动是否按它要求的编译期开关
(NO_LIGHT/NO_ADD_ACTION/NO_WIZARDS 已定义,OLD_ED/
PACKAGE_UIDS 未定义)编译,不满足就直接报错中止,而本项目的共享
驱动是按相反的开关编译的(其余所有 lib 都需要那一套)。
已经在 ~/src/fluffos-lima(~/src/fluffos 的一个独立 git worktree,
同一份源码不同的编译配置)按 Lima 的要求单独编译出了一份驱动,运行
时要用:
cd libs/lima
~/src/fluffos-lima/build-debug/src/driver config.fluffos(如果 ~/src/fluffos-lima 不存在,NOTES.md 里有完整的重建步骤,
一次性编译大约 5-10 分钟。)
游戏端口:40212。原生(native)驱动已验证。站点现在按 lib 覆盖
WASM 驱动(scripts/custom_drivers/lima_swmud/,与 swmud 共用),
浏览器试玩已走通注册→选种族→进入 Grand Hall,wasm_status 为
playable。详见 NOTES.md「站点基础设施缺口已补上」。
本地运行
cd libs/lima
~/src/fluffos-lima/build-debug/src/driver config.fluffos连接后按提示注册(英文 id + 密码 + 性别/邮箱/真实姓名等基本信息),
在用户菜单里用 c 创建角色、选择种族,s 选中、p 进入游戏。核心
指令:look、score(状态面板)、inventory、who、quit。
管理员账号 / Admin account
- id:
fluffos密码 / password:Mud@2026 - 权限 / level:
Admin(SECURE_D的domains["admin"]领域领主, 外加wizards表里的巫师身份)——通过直接编辑data/secure/access.o(Lima 自己的能力式安全存档文件)播种,因为 这套安全模型的正常自举方式是"全新安装后第一个创建角色的真人自动 成为 admin",本项目用固定账号覆盖了这个时序依赖。 - 由于
AUTO_WIZ默认开启,其实任何新注册的角色都会自动拿到巫师 权限——fluffos这个种子账号额外拿到的是更高一级的Admin(领域领主)身份,能使用admtool(管理菜单)等更高权限功能。 - 播种细节、验证过程见
NOTES.md「管理员账号播种」一节。
警告:这是一个公开的默认密码,仅供本地/浏览器试玩。正式对外开服前
请务必修改此密码。
NOTES · 移植与修复记录
lima — Lima Mudlib
来源:活跃上游是 limalib/lima(HEAD 98431aa05cc9,scheduled safe
rebase 2026-10-05 from eefaff66;
官网 <https://www.limamudlib.dev/>)。本馆可玩树最初从已归档的
fluffos/lima 克隆(dbcef2a Update fluffos/lima to 1.1a2 (#33),
2026-08-24)。编号 164,端口 40212。状态:done(干净启动,真实注册
流程 + 角色创建 + look/score/inventory/who/quit 全部验证可用;
管理员账号 fluffos 已播种并验证 update/admtool 等高权限指令)。
hosting 为 submodule-patch。leftover 684:work/ 已切成
limalib/lima submodule(pin 98431aa05cc9),config.fluffos 的
mudlib directory 指向 work/lib。
leftover 684:lima work/ 切到 limalib/lima submodule
按 leftover 682/683:非 fluffos 活跃上游保持他们的 .c,本馆兼容进
patches/。work/ 现在是 limalib/lima 的 gitlink,不再是本馆
.lpc 格式化树。catalog-only 补丁:
patches/0001-spell-d-move-ENSURE-comment-outside-macro.patch— 驱动预处理器吃不了ENSURE(x /* c */)(NOTES bug #4)patches/0002-troll-split-heredoc-start-marker.patch— 驱动词法吃不了@LONG text同行正文(NOTES bug #5 的 troll 处)patches/0003-remove-no_warnings-pragma.patch…0006-cmds-domains-contrib-fix-compile-warnings.patch— 2026-10-04:去掉#pragma no_warnings并修掉它遮住的编译警告(见文末同日一节)overlay/lib/data/—fluffos/Mud@2026Admin 播种 (access.o+links/f/fluffos.o+players/f/fluffos.o)
站点 zip 在 staging 上 apply patches + rsync overlay,所以下载仍是
可玩、可复现的源码树。autodoc.c 的行内 END 标记仍不编译,http_d
未 preload,不影响游玩。
leftover 683:归档的 fluffos/lima 不是 hosting 目标
fluffos/lima 2024-10-08 已 archived。按 leftover 682 的规则,归档 =
不再活跃,不能当 fluffos-upstream。活跃上游是同一 LIMA 社区的
limalib/lima(本馆 PR #49/#50/#51 已于 2026-09-03 合入)。meta.json
改为 hosting: submodule-patch,pin ffed9c588ee5。leftover 684 已把
work/ 切成该 submodule(见上一节)。
这个 lib 的架构,写给不熟悉 Lima 的读者
Lima 不是本项目里常见的"侠客行/金庸群侠传"系语系(LPMud 经典宗谱), 而是 FluffOS 驱动项目自己维护的官方参考 mudlib(<https://limalib.dev>), 英文,面向开发者/巫师的框架式设计,和本项目收录的绝大多数中文武侠 mudlib 在几乎每个层面上都不同:
- 没有
add_action()——Lima 的local_options要求驱动编译时定义NO_ADD_ACTION,房间/物品不用经典的"每个动词一个add_action"模式 注册命令。取而代之的是一个集中式自然语言解析器 (parse_command()/parse_sentence(),secure/master.lpc里的parser_error_message()/parser_gen_pos()等 apply 都是这个子系统 的一部分),能理解take the red potion from the box、put all coins in bag这类完整英语句子,而不是简单的"动词 宾语"。 - 没有传统的 uid 型 root 安全模型——
master.lpc没有实现get_root_uid()/get_bb_uid()(驱动为此打印一条良性警告),取而代之 是/secure/daemons/secure_d.lpc实现的一套自成体系的基于能力 (capability)的安全系统:privileges(谁拥有哪些权限字符串)、domains(谁属于哪个"领域"、谁是领域"领主")、protections(目录级 读写保护等级)、wizards(谁是巫师)。valid_read/valid_write全部 委托给这套系统而不是 uid 比较。文档见仓库自带的help/wizard/coding/security,写得相当详细。 - 巫师用的是类 Unix shell,不是巫师专属指令表——正常连接后进入的是
trans/obj/wish.lpc("wish",Lima 巫师 shell),支持变量 ($var = value)、管道、globbing、命令别名,提示符是? for shell help>。真正的游戏动词(look、移动方向等)在 shell 里 找不到命令对象时会 fallback 给上面说的自然语言解析器处理。 AUTO_WIZ默认开启——include/config.h里#define AUTO_WIZ是上游仓库自带的默认值(Lima 本来就是给开发者/巫师用的框架/演示环境, 不是要直接放给陌生玩家的正式游戏),意味着任何新建角色都会立即获得 向导(Wizard)权限。第一个在全新安装上创建角色的用户还会额外被 自动提升为Admin(domain lord,见secure/user/sw_body.lpcsw_body_handle_new_logon()的"no admin found"分支)——这是上游 故意如此设计,不是本项目引入的漏洞,在 README 里也做了说明。
§7.46 的后续:LIMA 系驱动编译选项冲突,这次真的解决了
AGENTS.md §7.46 早先记录过一个结论:"基于 LIMA 代码库的 mudlib 需要
本项目共享驱动没有的编译期开关(NO_LIGHT/NO_ADD_ACTION/
NO_WIZARDS/undef OLD_ED/undef PACKAGE_UIDS),不在源码层面可修,
需要一个单独编译的驱动 —— 当时判定为 out of scope"(案例:sgzmudsgz,
一个基于 LIMA 代码库的三国志 MUD)。这次直接拿到了 LIMA 本尊,所以把
这件事真正做掉了:
~/src/fluffos-lima:用git worktree add ~/src/fluffos-lima <与~/src/fluffos相同的commit>从共享驱动源码树建的独立工作树 (不是完整重新 clone,共享同一份.git对象库,但工作目录文件互不 影响,编辑其中的src/local_options不会污染其它 lib 用的~/src/fluffos/build-debug)。src/local_options改动(对照本项目默认驱动的原值):NO_ADD_ACTION/NO_WIZARDS/NO_LIGHT从#undef改为#define,OLD_ED从#define改为#undef。- CMake 配置额外加
-DPACKAGE_UIDS=OFF(默认 ON,Lima 自带的secure/check_config.c显式要求它关闭)。PACKAGE_PARSER/PACKAGE_CONTRIB默认已经是 ON,不用改。 - 构建:
cmake -S ~/src/fluffos-lima -B ~/src/fluffos-lima/build-debug -DCMAKE_BUILD_TYPE=Debug -DMARCH_NATIVE=OFF -DUSE_JEMALLOC=ON -DSTATIC=OFF -DPACKAGE_UIDS=OFF && cmake --build ~/src/fluffos-lima/build-debug --target driver lpcc -- -j$(nproc)。 - 验证:
~/src/fluffos-lima/build-debug/src/options.autogen.h里确认__NO_LIGHT__/__NO_ADD_ACTION__/__NO_WIZARDS__三个都出现(已定义),__OLD_ED__/__PACKAGE_UIDS__都不出现(未定义)——secure/ check_config.c的六个要求全部满足,不再在simul_efun编译期error()掉整个启动。
重要:libs/lima/ 不能用 ~/src/fluffos/build-debug(本项目其余
~240 个 lib 共用的默认驱动)启动——secure/check_config.c 会在
simul_efun 编译时因为上述六项全部不满足而报错并中止启动。必须用
~/src/fluffos-lima/build-debug/src/driver。这个 worktree 是本 session
新建的,不在 git 版本控制内(~/src/,不是 libs/lima/ 下),
如果这台机器/这个 checkout 丢失了要重新按上面步骤建一次——大概 5-10
分钟的一次性编译,之后复用。
WASM 状态当时标为 partial (native only):要让这个 lib 在浏览器里跑,
还需要在 emcmake cmake --preset wasm 的基础上叠加同样的
local_options/PACKAGE_UIDS=OFF 改动,构建一个专用的 wasm 驱动——
留作未来工作,这次 session 的任务范围(见协调者的原始指令)没有要求
WASM 通道。(2026-09-01 更新:这个专用 wasm 驱动后来真的建出来了、
且验证可用——见下方「wasm_status 审计」一节。partial 本身也不是这个
项目 wasm_status 枚举里的合法值之一,scripts/gen_site_index.py 的
EXCLUDE_STATUSES 把它当"deliberately deprioritized, e.g. ds386"处理
而不是"boots but WASM未验证"——当时这样写等于把这个 lib 从站点彻底
排除,而不是标成"限制",这正是本次审计要修的那类 wasm_status 误标。)
wasm_status 审计(2026-09-01):专用 WASM 驱动建成、但站点基础设施还接不上
之前 meta.json 的 wasm_status 一直留空——本节记录 2026-09-01 那次
批量审计([[project_wasm_status_audit]])对这个 lib 的完整调查过程和
最终结论:noboot(不是 playable,原因见下)。
专用 WASM 驱动:编译成功,mudlib 本身完全可玩
复用已有的 ~/src/fluffos-lima(git worktree,src/local_options 已经
按上面的六项要求改好):
cd ~/src/fluffos-lima
source ~/src/emsdk/emsdk_env.sh
cmake --preset native-tools && cmake --build --preset native-tools -- -j8
emcmake cmake --preset wasm -DPACKAGE_UIDS=OFF
cmake --build --preset wasm -- -j8(/opt/wasm-deps 下的 ICU/zlib 已经是全项目共享、编译好的,不需要
重新跑 tools/wasm/build-deps.sh。)一次性编译成功,产出
~/src/fluffos-lima/build-wasm/src/{fluffos.js,fluffos.wasm}。
第一次用 scripts/wasm_client.js 起跑立即命中和 ds386/dsII/dsIII
同一类"sockets 包缺失导致 simul_efun 编译失败"的 bug(AGENTS.md §1.3(c)
那一类,这次是首次在 lima 自身代码里发现,而不是三国/西游同人 mudlib
里的变体):secure/simul_efun/misc.lpc 的 dump_socket_status()
无条件调用 socket_status()——这个 efun 只在编译了 sockets 包的
驱动上存在(WASM 构建默认不带 sockets 包,浏览器沙箱里没有真实
socket 的语义),所以这不只是运行时缺失,而是编译期就报
"Undefined function socket_status",导致整个 secure/simul_efun
编译失败(*No program in object '/secure/simul_efun/misc'!),
驱动直接拒绝启动("The simul_efun ... and master ... objects must be
loadable")。修复(#ifdef __PACKAGE_SOCKETS__ 包一层,没有这个包时
退化成返回空字符串,和 ds386 已有的同类修复手法一致):
string dump_socket_status()
{
#ifdef __PACKAGE_SOCKETS__
... (原有实现,逐行不变) ...
#else
return "";
#endif
}修复后用 wasm_client.js 完整跑通一次真实会话:fluffos/Mud@2026
登录 → 用户菜单等到 auto-login 10 秒倒计时自动进入游戏(也可以直接
输入 p 跳过倒计时)→ 落地 Grand Hall → score 显示完整的属性/
经验/负重面板 → quit 干净退出("You have left Lima Mud."),全程
driver_stdout.log 无一条运行期错误。这套 mudlib 内容本身在专用
WASM 驱动上是完全可玩的——之前 README 写的"WASM 通道未构建"这句话
现在已经不成立了,专用驱动确实建出来了且验证通过。
原生驱动(~/src/fluffos-lima/build-debug/src/driver)用同样的
fluffos/Mud@2026 账号复测,无回归。
但站点实际部署用的是唯一一份共享驱动,这个专用驱动接不进去
问题出在这个项目的站点打包管线,不在 lima 自己身上:
scripts/write_play_page.sh 把每个 lib 的 play.html 写进站点时,
里面的驱动脚本引用是写死的相对路径 ../_driver/fluffos.js——_driver/
是整个站点唯一一份共享的 WASM 驱动产物(来自
~/src/fluffos/build-wasm),所有 ~240 个 lib 共用同一份二进制,
没有任何"这个 lib 用另一份驱动"的每-lib 覆盖机制(build_site.sh/
write_play_page.sh 通读一遍,找不到任何 per-lib driver 路径参数)。
secure/check_config.lpc 的六项检查(NO_LIGHT/NO_ADD_ACTION/
NO_WIZARDS/OLD_ED/PACKAGE_UIDS)在任何驱动上、在 create()
里都会无条件跑一遍,不分 native 还是 WASM——用共享站点驱动(没有按
lima 的要求编译)跑 lima,这个检查一定会 error() 掉,跟本地这次
用专用驱动之前遇到的、AGENTS.md §7.46 记录的"驱动编译选项冲突"是
同一个错误。也就是说:即使这次把专用 WASM 驱动建出来了,部署到
mudlibs.fluffos.info 上的那份 lima 实际上还是会用共享驱动去启动,
一样会在 simul_efun 编译期报错中止——wasm_status: playable 会是
一句谎言,不代表访客点开这个 lib 的页面真的能玩。
结论:wasm_status 设为 noboot
按审计任务给出的定义("noboot: 即使 native 能跑,WASM 下也跑不
起来")——这里的"WASM"应该理解成"这个项目实际部署的 WASM 环境",也就是
共享驱动,而不是"技术上存在某种 WASM 驱动配置能跑"。专用驱动的存在
证明了 mudlib 内容层不是问题,问题完全在站点基础设施的"一份驱动打包
所有 lib"这个假设上,这已经超出本次审计("修正个别 lib 的
wasm_status 字段")的范围,属于一次单独的站点管线改造工作(给
write_play_page.sh/build_site.sh 加一个 per-lib 驱动目录覆盖
参数,再单独为 lima 归档 ~/src/fluffos-lima/build-wasm 的产物)。
把这个专用驱动的完整构建步骤记在上面,方便未来那次改造直接复用,
不用重新摸索 check_config.lpc 的六项要求或重新踩一遍
__PACKAGE_SOCKETS__ 这个坑。
secure/simul_efun/misc.lpc 的这处修复本身不依赖站点基础设施,已经
提交进 libs/lima/work/——不管未来站点管线怎么改,这个修复都是
必需的前置条件,先做掉不亏。
站点基础设施缺口已补上(2026-09-02/03):wasm_status 翻正为 playable
上一节末尾说的"未来那次改造"这次做掉了:write_play_page.sh 新增
一个可选的第 5 个参数(自定义驱动目录),给出时把该目录下的
fluffos.js/fluffos.wasm 拷进这个 lib 自己的站点输出目录,
play.html 里的驱动脚本引用也相应改成本地路径而非共享的
../_driver/(telnet.js/vendor/xterm 这些和驱动编译选项无关的
文件仍然共享)。build_site.sh 新增一个 custom_driver_dir_for()
查找表,lima/swmud/spacemud 三个 slug 映射到
scripts/custom_drivers/lima_swmud/——本节上面记录的那份专用驱动
产物(~/src/fluffos-lima/build-wasm/src/{fluffos.js,fluffos.wasm},
2026-08-24 的 fluffos/fluffos@721878c 构建)直接提交进了这个目录,
不需要在 CI 里重新编译(CI 没有装 emsdk,见 AGENTS.md §1.6)。
本地用 pack_lib_zip.sh + write_play_page.sh <slug> ... <custom_dir>
搭了一个范围内(只有这两个 lib)的测试站点验证:fluffos.js/
fluffos.wasm 正确落地在 lib 自己的输出目录,play.html 引用也正确
指向本地而非 ../_driver/。用 Playwright 通过真实的 #cmd 输入框
走完整个流程——注册、性别/种族选择、创建角色、s+p 进入游戏——
落地 Grand Hall,look 正常显示房间描述,和本节前面用
wasm_client.js 跑通的那次结果完全一致,控制台无 JS 报错。
meta.json 的 wasm_status 从 noboot 改为 playable。见
libs/swmud/NOTES.md 对应小节(同一份驱动,独立验证)。
转换步骤(对照 AGENTS.md §2/§2.3)
1. 克隆到 scratch 目录,lib/ 就是 mudlib 根(README 自带的
mudlib directory : ./lib);driver/(fluffos 子模块)、
resources/、build.sh 等构建脚手架按约定忽略,只取 lib/。
2. 编码:整个仓库 1969 个文件里只有 2 个非 UTF-8——WWW/lima.jpg
(二进制图片,正常)、help/wizard/coding/parser(一处 Latin-1
版权符号 ©,iconv -f latin-1 -t utf-8 修复)。其余全部原生
ASCII/UTF-8,符合"最新 fluffos 兼容的现代仓库"预期,不需要
GB18030/BIG5 转换。
3. .c → .lpc:scripts/convert_lib.sh(用 UTF-8 作为"源编码"
跑一遍,等价于跳过转码、只做改名 + 引用修复),1095 个 .c 文件
全部重命名,118 处字面量 .c" 引用自动修复,0 处遗留。
- 发现一处 §4.2 条目 5("扩展名缺失文件 + 同名 .c 备份")的实例:
secure/daemons/finger_d(无扩展名)与 finger_d.lpc(原
finger_d.c)内容不同。git log 确认 finger_d.c 是最近一次
commit(Update fluffos/lima to 1.1a2 #33)改过的版本,无扩展名
的那份自 #28 之后再没被碰过——是过时内容,已删除。
- static -> nosave 批量替换踩了 §4.3 描述的"字符串字面量误伤":
domains/std/lima/workroom_ob.lpc 一句房间描述文本里恰好出现了
单词 static("a burst of static darts across your screen"),
被误替换成 nosave,已手动改回。include/global.h 里
#ifndef __SENSIBLE_MODIFIERS__ #define nosave static #define
protected static #endif 的兼容 shim(§4.3 第二类碰撞)也被误伤成
#define nosave nosave,同样改回原文——不过这个 shim 本来就在
#ifndef __SENSIBLE_MODIFIERS__ 保护下,本项目驱动始终定义
__SENSIBLE_MODIFIERS__,这段代码从未真正执行,改不改都不影响
行为,纯粹是为了保持源码忠实。
编译期发现的真实 bug(而非内容/示例缺口)
以下几处是驱动兼容性/程序错误,符合本项目"只修程序 bug,不改 内容设计"的范围,已修复:
1. .c→.lpc 改名的连锁反应:命令查找机制本身就没跟上扩展名变长
(AGENTS.md §4.2 第4类"固定宽度切片而非扩展名操作",这次是同一类
bug 在上游自己的 .c-命名树里原本"恰好正确",被本项目的重命名
直接触发)。secure/daemons/cmd_d.lpc 的 cache_dir()
用 get_dir(dir+"?*.lpc") 拿到文件名后用 $1[0..<3] 砍掉扩展名——
这个切片宽度是为两个字符的 .c 算的(<3 在这个驱动的区间
语法里表示"从末尾数第 3 个字符(含)到开头",对 5 字节的 foo.c
算出 foo),改名后文件变成 4 字节的 .lpc,同一个 <3 切出
score.l 这种带垃圾尾巴的"命令名",导致几乎所有裸词形式的
巫师/玩家命令全部失效——score、inventory 等在 shell 里输入
会报 I don't know the verb 'score',只有写全路径
/cmds/player/score 才能跑(score.lpc 本身完全正常,纯粹是
路径匹配层面的缓存 key 算错了)。这是这次转换里影响面最大的一个
bug——不修的话整个游戏对话式命令行基本不可用。同一切片模式在
cmd_d.lpc 另一处(smart_arg_parsing 里按绝对路径匹配后取
cmd_name)、daemons/spell_d.lpc(重建法术目录缓存)、
daemons/quest_d.lpc(显式 if (base[<2..]==".lpc") 判断之后却用
两字节宽度去砍)里各出现一次,一并改成 <5(对应 4 字节的
.lpc)。已用真实驱动重新启动、注册、score/inventory/update
等裸词命令验证全部恢复正常。全仓库还有 27 处同形态的 [0..<3]
切片,逐一读取上下文后确认其余都是无关的字符串截断(去掉末尾逗号、
"\n\n"、"::" 等),或是针对 .o(SAVE_EXTENSION,仍是 2 字节,
不受影响)——只有以上 4 处真的和 .c→.lpc 改名相关。
不提交上游:这是本项目自己 .c→.lpc 改名操作触发的问题,
上游 fluffos/lima/limalib/lima 原生文件名仍是两字节的 .c,
[0..<3] 在那里本来就是对的——这是本项目的转换产物,不是 lima
自己源码里的 bug,不属于这次 upstream PR 审计范围(2026-08-31)。
2. set_droppable() 参数类型声明过窄:std/modules/m_gettable.lpc
自己的文档注释写"传函数或字符串等效于调用 set_dropmsg()",函数体
也确实用 functionp(g) || stringp(g) 判断,但签名却声明成
void set_droppable(int g)——这个驱动的静态类型检查因此拒绝
domains/std/lima/directory.lpc/workroom_ob.lpc(后者正是新巫师
自己的 workroom)里传字符串自定义"拿不下来"提示语的调用。参数类型
改成 mixed g,和函数体实际行为、文档注释保持一致。
已提交上游:fluffos/lima 已于 2024-10-08 被官方归档只读
(gh pr create 直接报 GraphQL 错误"Repository was archived so is
read-only"),无法开 PR。经核实,同一 LIMA 社区(同一 Discord 频道
#LIMA、同名品牌"limamudlib.dev")已迁移到活跃维护的
limalib/lima(非 fork 关系但代码血缘一致,git clone 核对确认
m_gettable.c/m_exit.c/usermenu.c 三处逐字节相同,同一 bug
原样存在),已改为向 limalib/lima 提交:
limalib/lima#49。
3. private 跨文件继承调用(AGENTS.md §7.48 标准案例):
std/modules/m_exit.lpc 的 eval_dest() 声明为 private,却被
继承它的 std/modules/m_exit_obj.lpc 直接调用——这个驱动对
private 的强制比老 MudOS 严格(继承链内也不能跨文件调),改成
protected(比 private 宽松,不会引入新 bug,只会修好已经存在的
非法调用错误)。
已提交上游:同上,fluffos/lima 已归档,改为
limalib/lima#50。
4. 含内联注释的宏参数展开异常:daemons/spell_d.lpc 里
ENSURE(spell_name /* 注释 */) 这种"宏参数里带 C 风格块注释"的写法
触发这个驱动预处理器的一个真实 bug——ENSURE 宏没有正常展开,报
Undefined function ENSURE 后级联出语法错误,导致
daemons/spell_d.lpc 本身以及依赖它的 6 个法术文件全部编译失败
(fireball/magic_arrow/unlock/test/ale 及 stock-mage 分支)。
已构造最小复现(两行 LPC:ENSURE(x /* c */);)确认与具体文件内容
无关,是宏参数解析器本身在"参数里含 /* */"时的通用问题——不打算在
本项目范围内修驱动本身(那是 fluffos/fluffos 的事,需要走 PR),
采用等价、保留原意的写法规避:把注释挪到宏调用外面
(ENSURE(spell_name); /* 注释 */),两处均已修复并验证。
不属于本次 lima queue 审计范围:这是 fluffos/fluffos
驱动本身的 bug,不是 fluffos/lima/limalib/lima 源码的 bug——
本次任务(2026-08-31)只审计 lima 的 mudlib 源码是否需要提 PR,
驱动仓库层面的 PR 不在这次的范围内,未提交。
5. 含"开括号后紧跟内容"的 heredoc(@TAG content...)在这个驱动的
词法分析器里同样有真实问题:std/race/troll.lpc
(@LONG Trolls are...)和 WWW/cgi/autodoc.lpc(7 处
@END<title>...)在开始标记同一行紧跟正文时报
End of file in text block——已用两行最小复现确认(@LONG hi\nthis
is\na test\nLONG; 能编译,把首行改成 @LONG hi 同一行接内容就不能)。
同样倾向于判定为驱动词法分析器的真实 bug(不在本项目范围内修驱动,
fluffos/fluffos 需要走 PR),mudlib 侧用等价、保留原文内容的写法
规避:把开始标记单独放一行,正文从下一行开始。troll.lpc 已完全
修好(唯一一处),autodoc.lpc 的 7 处开始标记都修了,但该文件的
HTML 内容里结束标记本身也不是顶格(比如 <UL> END
+,END 前面还有真实 HTML 内容),而这个驱动的 heredoc 结束标记
必须是整行的最前面——这是另一类、更深的方言差异(老式 LPC 允许
结束标记出现在缩进/行内任意位置,这个驱动不允许),不是简单挪一行
能解决的,autodoc.lpc 因此仍然编译失败。鉴于它只是一个默认未启用
(http_d 在 preload 里被注释掉)的巫师专用 CGI 文档生成工具,
不影响核心游玩,这里记录下来但不再深入修——留给以后需要真正用到
web 文档生成功能时再处理。
不属于本次 lima queue 审计范围:同上一条,这是 fluffos/fluffos
驱动词法分析器本身的 bug,不是 lima 源码的 bug,本次审计
(2026-08-31)未提交驱动仓库层面的 PR。
6. 赋值/比较符打错:obj/usermenu/usermenu.lpc 的
confirm_decision()——if (dec == "yes" || dec = "y"),第二个条件
是赋值不是比较,导致这个函数永远返回真,无视用户实际输入的
"确认删除角色"对话框形同虚设(remove_char 流程里的 yes/no 确认,
不管输入什么都会被当作"是")。改成 ==。
已提交上游:同上两条,fluffos/lima 已归档,改为
limalib/lima#51。
§2.2 On-sight checklist 结论
master.lpc没有get_root_uid()/get_bb_uid()(驱动打印良性警告, 见上文"架构"一节——这套 mudlib 本来就不走 uid 安全模型)。- 没有
destruct(simul_efun)相关问题;secure/simul_efun.c通过private inherit check_config+ 一串inherit simul_efun/{overrides,security,objcalls,userfuncs,path,string,misc, more,tell,goofy,json}组装,check_config的驱动兼容性自检见上文 "§7.46 的后续"一节。 this_user()在secure_d.lpc里被大量、正确地用在权限校验链的 末端(check_privilege()的调用栈遍历),符合预期。- preload 列表(
data/config/preload)没有 dns/intermud 相关裸目录 引用问题;daemons/imud_d因为ADMIN_EMAIL仍是上游占位符[email protected]而拒绝加载(imud_d.lpc自己的保护性检查, 报错信息很清楚)。保留原样未修——这是刻意的自我保护 (防止误配置的 mud 用假地址骚扰 Intermud 网络),且本项目对其余 ~240 个 lib 的标准策略本来就是"DNS/intermud 排除在 preload 之外" (§7.6),imud_d 加载失败只是让 preload 的catch()静默跳过,不 影响其余功能,不需要改动config.h让它连上真实的公网 I3 网络。 - 中文检测/
check_legal_name相关条目不适用——这是纯英文 lib。 - 命令派发(cmd_d.lpc 的
private/nomask command_hook类问题): 见上文 bug #1(cache_dir 切片宽度),是这次唯一命中的、而且是 影响面最大的一类。 - 端口硬编码:
config.lima/include/config.h都没有硬编码端口常量, 端口完全由config.fluffos的port number决定,改起来没有陷阱。 static/nosavecollision:见上文转换步骤一节,已处理。ed_start/ed_cmd/query_ed_mode:存在(trans/obj/ ed_session.lpc等 6 个文件),但这次驱动是专门按 Lima 自己的local_options要求#undef OLD_ED编译的,这三个 "new ed" efun 在这个专用驱动上是真实存在的(core.spec里#ifdef OLD_ED ... void ed(...) #else string ed_start(...) ...), 不是 §6.2 描述的那种"这个驱动只有老 ed()"的情况——这正是这次专门 建一个 Lima 专用驱动要解决的问题之一,编辑器/update/ed命令都 验证可用。
管理员账号播种
Lima 自己的安全模型是"能力(capability)+ 领域(domain)"式的,没有
本项目其它 lib 常见的"adm/etc/wizlist"或"(admin) 状态字符串",
而是 /data/secure/access.o(SECURE_D 的存档文件,
ACCESS_SAVE="/data/secure/access")里的 wizards 和
domains["admin"] 两个 mapping。上游仓库自带的原始档案是一个"全新
安装、什么人都没有"的空白状态(wizards ([]),domains["admin"]
([]))——这本来就是设计使然:secure/user/sw_body.lpc 的
sw_body_handle_new_logon() 里有一段"没有任何 admin 时,把第一个创建
角色的人自动提升为 admin"的引导逻辑(自举机制),正常情况下靠"第一个
真人巫师创建角色"就能完成播种,不需要额外操作。
但本项目的标准约定是固定的 fluffos/Mud@2026 账号,而不是"谁先手快
谁当家"——为了不依赖"谁第一个连上来"这种时序,直接编辑
data/secure/access.o(纯文本的 save_object() 格式,逐行
变量名 值 ),把 wizards 加上 "fluffos":1,把
domains["admin"] 加上 "fluffos":2(2 = 领域领主/lord,和
add_domain_member(domain, member, lord) 的语义一致)。这样一来,
fluffos 一注册就直接是 admin,同时因为 domains["admin"] 已经非空,
"自动提升第一个人"的引导逻辑不会再对后续任何测试账号触发(已用
traveler/finaltest 两个测试角色验证:它们注册后只拿到普通
AUTO_WIZ 赠送的 Wizard 权限,who 里 Role 显示 Wizard,不是
Admin——引导逻辑确实只认第一次)。data/secure/access_backup.o
(save_data() 顺带写的备份文件,启动时不读取)同步更新以保持一致,
测试期间产生的其它账号相关残留(traveler/finaltest 的存档、
data/referrals、data/secure/LOG、data/daemons/last_login.o)已
清理,只留 fluffos 一个种子账号。
验证:fluffos 登录后 who 显示 Role: Admin;update
/std/race/human(读+写都要过 ACL 的规范检验)和 admtool
(进入管理菜单,能看到 1 - priv 1 选项)均成功,说明播种的不只是
"看起来像 admin"的展示状态,写权限确实生效。
已知但未修的内容/示例缺口(按项目惯例:不是程序 bug 就不动)
编译扫描(lpcc --batch,1095 个 .lpc 文件)在上述 bug 修复后还剩
38 个失败,逐一读取错误信息确认全部是内容/示例本身的缺口,不是
驱动兼容性问题:
domains/std/school/(新手教程区)、domains/std/rooms/{caves, labyrinth}里少数房间引用了未提供的辅助函数(start_decay、set_default_exit、ROOM全局量),像是这套教程区自己就没写完的 演示内容。contrib/(bboard、marriage、roommaker、transient_effect等)——仓库自己就标成"贡献/可选扩展"目录,contrib/marriage/ finger_d.lpc引用了一个不存在的class finger成员spouse,像是 一次不完整的重构遗留。domains/std/spells/stock-priest/——std_priest_spell.lpc调用set_cast_time()/set_difficulty(),但全仓库没有任何地方定义这两个 函数,法术系统这一分支本身就没写完。cmds/player/attic/、domains/std/attic/——目录名本身就是"阁楼/ 废弃",m_react.lpc(domains/std/attic/{beavis,butthead}用到)里 确实有一处看起来是真实类型错误(x++用在字符串上),但鉴于这些是 两个彩蛋 NPC 用的、已标记废弃的模块,判定为"内容归档"范畴,不在这次 的核心游玩验证路径上,不处理。obj/secure/mailers/{mailer,Mh,standard}.lpc——邮件撰写用到一个 全仓库都不存在的trim_spaces函数,且有一处"functional 里用局部 变量"的驱动限制。imail_d本身正常 preload(收发基础设施没问题), 但撰写新邮件这个具体功能路径有缺口。因为这属于要新写一个辅助函数、 猜测其确切行为的范畴,不是一处明确的打字错误,按"内容/设计判断, 不确定就不动"的原则记录、不修。WWW/cgi/{autodoc,mudinfo}.lpc——见上文 bug #5 的详细说明;http_d(唯一会用到这些 CGI 脚本的daemon)默认在data/config/preload里被注释掉,不影响核心游玩。secure/daemons/ftp_d.lpc——一处"functional 里用局部变量"的驱动 限制,ftp_d同样默认不在 preload 列表里。
定义完成的验证记录
用 scripts/mudclient.py 通过真实驱动完整走过三轮独立账号
(questor→wanderer→traveler,以及最终的 fluffos/finaltest):
注册(id/密码/性别/邮箱/真实姓名/主页/推荐来源全部走完)→ 用户菜单
c 创建角色 → 种族选择(human/elf 两种都测过)→ s 选择角色 → p
进入游戏 → 落地在 Grand Hall(Lima Bean 巫师聚会场所,游戏内自带的
"新手/巫师起始房间",因为 AUTO_WIZ 的关系每个新角色都从这里开始)→
look(房间描述正确重复)→ score(ASCII-art 状态面板,含种族对应的
六维属性、经验、卡玛值)→ inventory("You are empty handed.")→
who(正确列出在线角色和权限等级)→ quit(正常退出,
[announce] 频道正确广播离线消息,回到用户菜单,debug.log 全程
无报错)。管理员账号额外验证 update/admtool。整个流程干净、无
崩溃、无静默失败。
附录(2026-08-26):limalib/lima("modernized LIMA" fork)调查结论 —— 负面结果,未新建 lib
外部研究提到一个 GitHub 仓库 limalib/lima(<https://github.com/limalib/lima>,
自称 "Updated, maintained, modernized LIMA mudlib updated to run latest
FluffOS driver. Kept in the original LIMA spirit.",官网
limamudlib.dev,文档 docs.limamudlib.dev,HEAD eb25dc6,2025-10-27),
怀疑这个"现代化"分支是否已经从根本上摆脱了 §7.46 记录的驱动编译期开关
冲突(本条目上文已经用的那五项:NO_LIGHT/NO_ADD_ACTION/NO_WIZARDS/
undef OLD_ED/undef PACKAGE_UIDS)。本次任务专门验证了这一点——
结论:没有,问题原样保留,limalib/lima 和 libs/lima 用的
fluffos/lima 一样,需要同一套专用驱动才能启动,不构成新的可独立
onboard 的 lib,因此本次只写这条负面结论,不新建 libs/<slug>/ 目录。
验证过程
1. 代码血缘对比:limalib/lima 和本 lib 来源的 fluffos/lima
是两个独立的 GitHub 组织下的仓库(limalib/lima 的
defaultBranchRef/parent 显示不是 fluffos/lima 的 fork),
但目录结构和核心文件几乎一致——两边的 secure/check_config.c
逐行 diff 下来,除了版权头文字和版本号字符串("MudOS"→"FluffOS"、
config.lima→config.mud)之外,need() 检查列表完全相同,
证明是同一 LIMA 血缘的演进版本,不是独立重写。文件数
limalib/lima 1534 个 vs fluffos/lima 1969 个(前者砍掉了不少
demo 内容,新增了 obj/admtool、obj/tasktool、std/race、
cmds/guild 等框架性目录),属于同一代码库的功能增补/精简,
不是从零重写的替代架构。
2. 直接读 limalib/lima 自带的 lib/secure/check_config.c——
这就是 LIMA 系 mudlib 用来拒绝在不兼容驱动上启动的自检文件。
除了沿用 §7.46 记录的五项(NO_LIGHT/NO_ADD_ACTION/NO_WIZARDS/
undef OLD_ED/undef PACKAGE_UIDS),还新增了几项检查
(SANE_EXPLODE_STRING 需定义、CAST_CALL_OTHERS 需未定义、
OLD_RANGE_BEHAVIOR 需未定义、MUDLIB_ERROR_HANDLER 需定义、
ARRAY_RESERVED_WORD 需未定义、PACKAGE_CONTRIB/PACKAGE_PARSER
需定义)——但这些新增项本项目共享驱动(~/src/fluffos/build-debug)
原本就满足,不构成新的冲突点;真正冲突的仍然是那五项经典要求,
一个字都没变。
3. 实测启动:用本项目的 scripts/convert_lib.sh 把
limalib/lima 的 lib/ 转成 .lpc(UTF-8 源码,1407 文件已是
UTF-8、0 需要转码,127 个二进制文件跳过,行为和 fluffos/lima
转换时几乎一样),配上一份仿照本 lib config.fluffos 改路径/端口
的临时配置,直接用本项目共享的标准驱动
(~/src/fluffos/build-debug/src/driver,未加任何特殊 flag)尝试
启动——simul_efun/master 加载阶段立即被 check_config.lpc
的 create() 用 error() 中止,实际报错文本:
``
*Bad driver configuration:
**********************************************************
* You have incorrectly compiled the FluffOS driver. This *
* driver is not compatible with the LIMA mudlib. Please *
* make the following changes to 'local_options' ) in the *
* driver source, and recompile. *
********************************************************
#define NO_LIGHT is required for LIMA libs.
#define NO_ADD_ACTION is required for LIMA libs.
#define NO_WIZARDS is required for LIMA libs.
#undef OLD_ED is required for LIMA libs.
#undef PACKAGE_UIDS is required for LIMA libs.
********************************************************
The simul_efun (/secure/simul_efun) and master (/secure/master) objects must be loadable.
``
和 §7.46/本 lib 上文记录的 fluffos/lima 症状完全一致——不是
"modernized" 架构层面移除了这个假设,只是版本号/驱动兼容性提示文字
更新了而已(README 里"modernized"、"run latest FluffOS driver"
说的是能跟着最新版 fluffos 驱动源码(adm/dist/fluffos 子模块跟踪
fluffos/fluffos.git)编译,而不是摆脱 NO_LIGHT/NO_ADD_ACTION/
NO_WIZARDS/OLD_ED/PACKAGE_UIDS 这套架构假设)。
有用的推论(供未来参考)
limalib/lima 要求的五项和本 lib 已经解决的 fluffos/lima 要求
完全相同,所以本 session 已经建好的
~/src/fluffos-lima/build-debug(worktree,见上文"§7.46 的后续"一节)
理论上不需要任何改动就能同时满足 limalib/lima 的 check_config.c——
如果未来真的要把 limalib/lima 也收进本项目(例如作为它自己独立的
lib,因为它的功能集/demo 内容和 fluffos/lima 已经有实质差异,不是
纯粹的重复),可以直接复用这个驱动 worktree,不必重新摸索 flag 组合。
但这次任务的范围只是回答"是否解决了驱动 flag 冲突"这一个问题,
结论是否定的,所以没有走完整的 onboard 流程(没有 convert_lib.sh
之后的编译修复循环、没有真实注册验证、没有分配 number/port、
没有新建 libs/<slug>/ 目录)。
深度功能测试 / Deep functional test (2026-08-27, §10.7)
这个 lib 之前只做过"能启动/编译"级别的验证(见上文"定义完成的验证
记录"一节)——本轮是第一次按 AGENTS.md §10.7 的方法论做一次真正连续
的真人playthrough(真实注册 → 探索 → 战斗 → 门派/公会交互 →
quit/隔一段真实时间后重连 → 查日志),用专用的 ~/src/fluffos-lima
worktree 驱动(已存在,直接复用,未重新编译)。
Lima 本身是 FluffOS 官方的框架/演示 mudlib,不是一款有完整门派/任务
系统的"游戏"——help newbie 就是原版英文 Zork 式通用教程,核心可测
系统是:注册/建角、AUTO_WIZ巫师身份、导航、skills(战斗中自动
熟练度成长)、kill战斗、GUILD_GUARD门禁、talk toNPC 对话、
quests、quit/重连。测试角色:账号 TestHero/TestPass123,角色
Aidan(human),全程走过注册六项资料 → 建角(种族选择)→ s选中 →
p进入游戏,落地 Grand Hall;另建了一个非管理员的 questfinal/
Rowan 角色专门复测下面的 bug 修复(确认不是只有先前角色的残留状态
碰巧绕过了问题)——两个测试账号连同它们的存档在验证完成后都已按本
项目惯例清理(data/players/{a,r}/、data/links/{t,q}/、以及
GUILD_D 因这次测试而首次落盘的 data/daemons/guild.o),只保留种子
fluffos 管理员账号;data/secure/{access,access_backup}.o、
data/secure/LOG、data/daemons/last_login.o、data/news/*
里因登录/公会daemon初始化产生的残留字段也已用 git checkout
还原,最终 git status 只剩两处真实代码改动。
发现并修复的真实 bug
1. std/guild_guard.lpc 的 handle_blocks() 对一个从未真正被
GUILD_D 定义过的公会名,会把驱动的未捕获 error() 直接甩给玩家,
导致"从被公会守卫挡住的房间试图离开"这个完全普通的移动动作每次必
崩溃。
- 复现路径:Grand Hall 向南进入
domains/std/Monster_Room(这个房间 的 hint 文本自己写着"This room is often used to test combat system mechanics",是官方给的战斗测试区),房间里的guild_guard(domains/std/Monster_Room.lpc:13,set_objects((["/domains/std/ guild_guard":({"sorcery"})])))挡住唯一的北向出口,要求"sorcery" 公会成员身份。而data/config/guild-spec/stock-guilds只是示例数据文件,从未被任何 preload/create() 调用GUILD_D->define_from_file()加载,所以"sorcery"这个公会在GUILD_D里根本不存在。任何角色(无论是先打赢了这个 5 HP 的守卫, 还是干脆直接走)只要尝试往北走,guild_guard.lpc:22的GUILD_D->query_guild_allies(guard_for)就会一路调用到daemons/guild_d.lpc:269的guild_check()——这个函数对不存在的 公会名统一error("Non-existant guild - " + name + ".\n"),而handle_blocks()完全没有 catch 这个调用。结果:一个真实的、未捕获 的驱动错误,栈顶层展开后还级联出第二个无关错误 (/cmds/verbs/go.lpc报Parse accepted, but no do_* function found in object /cmds/verbs/go!,是第一个错误把do_go_str()的执行流打断后遗留的状态不一致)。玩家因为AUTO_WIZ默认订阅了 errors 频道,会直接在自己的终端上看到两段完整的驱动栈 跟踪——比"你被挡住了"这种正常游戏反馈严重得多的失败模式。 - 对照:同一个文件里
query_member_guild()(std/body/guilds.lpc) 对一个不存在的公会名已经优雅地返回0("不是成员"),说明handle_blocks()本来的意图就是"公会不存在=当作没有这层关系", 只是它调用query_guild_allies()时漏做了同样的防御。 - 修复:给
GUILD_D->query_guild_allies(guard_for)这次调用套一层catch(),出错时把allies当作空数组({})处理(等价于"这个 公会没有盟友"),而不是让错误继续向上传播。不改变guild_d.lpc本身任何一个guild_check()的行为(它对公会管理类接口——比如set_guild_title()——保持严格报错 仍然合理,那些接口本来就该假设调用者知道公会存在),只在这个 "查询式、应该能对不存在的东西安全返回"的调用点做防御。 - 验证:
Aidan(打赢过守卫)和全新角色Rowan(完全没打过架,直接 走)两条路径分别复测——修复前两者都在north时触发上述两段完整 错误栈;修复后(真实重启驱动生效,LPC 改动无需重新编译 C++)两条 路径都只剩一条可控的信息性日志([errors]频道仍会显示 "Non-existant guild - sorcery.",因为mudlib error handler : 1这套配置下即使catch()吞掉的错误也会记录/广播给订阅了 errors 频道的账号,这是这套 mudlib 自己一贯的调试可见性设计,不是本次改动 引入的噪音),玩家实际收到的是预期的游戏反馈:"The a guard pushes you back. \"Guild members only\", she growls."——移动被正确拒绝, 而不是崩溃。(旁注:这条拒绝消息本身有个"The" + "$n"→"a guard" 重复冠词的措辞小瑕疵,set_block_action("The $n $vpush $t back...")——纯文案问题,不影响功能,按本项目"内容/设计不确定就不 动"的原则只记录不修改。) - §9 格式化器:按流程对这两个改动过的文件跑了一次 §9 LPC formatter,
guild_guard.lpc结果正常,但guild_d.lpc触发了一个新的格式化器盲点——class guild_defn { ... }这种带内联注释的多字段 struct 体被错误地压扁成一整行后,格式化器的 花括号配对状态发生错位,导致后面几十个完全无关的函数 (belongs_to/check_previous/guild_add/… 一直到文件结尾)被 级联误加了一层缩进(本身是纯空白改动,语法仍然合法,但 diff 膨胀到 1700+ 行,完全掩盖了这次两行真实修复的意图,且没有信心排除格式化器 在别处发生了真正的语义级损坏)。按 AGENTS.md §9 "遇到盲点用git checkout整个文件回滚,不要手工修补格式化器的输出"的既定处理方式,git checkout回滚了两个文件的格式化结果,手工原样重新应用了上面 两处最小 diff(未跑格式化器),保留这两个文件原有的缩进风格不变。 这是这个格式化器的一个新盲点实例(多字段classstruct 体 + 内联注释导致花括号跟踪状态错位),值得以后遇到class定义密集的 文件时留意,但由于 Lima 是本语料库里唯一一个用这种框架的 lib,暂不 在 AGENTS.md §9 补充通用条目。
2. daemons/guild_d.lpc 的 load_missions()/load_favors()
两个函数在 GUILD_D 第一次被懒加载时必定崩溃,因为它们引用的两个
目录在这个仓库里根本不存在——修复 #1 直接触发并暴露了这个第二层
bug。
- 上面 #1 的
catch()第一次真正把GUILD_D加载进内存时(此前没有 任何 preload 项引用过它),GUILD_D::create()(daemons/guild_d.lpc:115)依次调用load_missions()/load_favors()。load_missions()用get_dir(MISSION_DIR "*.lpc")(MISSION_DIR=/domains/common/mission/)取文件名列表,但这个仓库压根没有domains/common/这个目录——get_dir()对一个不存在的目录返回的是0,不是空数组({}),紧接着的foreach (string item in files)直接报*Bad argument 2 to foreach Expected: array Got: 0.,把整个GUILD_D的create()炸掉(load_favors()用FAVOR_DIR=/domains/common/favor/,同一目录也不存在,是完全相同 的第二处实例)。这正是 AGENTS.md 里已经归档过的"get_dir()对不存在目录返回0而非空数组"经典坑(见 AGENTS.md 相关条目), 这次是它在 Lima 自己的核心 daemon 代码(不是某个具体 domain 的示例 内容)里的一个新实例,而不是需要单独立项的新 bug 类别,故未新增 AGENTS.md 条目,仅在此交叉引用。 - 修复:
load_missions()/load_favors()各加一行if (!files) files = ({});,get_dir()返回目录存在但为空时本来就 已经是({}),这个 guard 只处理目录整个不存在的那一种情况,行为上 完全是"这两类内容还没人往这仓库里加"的空操作,不是猜测应该往里面 放什么。 - 验证:修复后重启驱动,
GUILD_D第一次被上面 #1 的调用路径懒加载时不再报Bad argument 2 to foreach,load_missions()/load_favors()静默返回,GUILD_D正常初始化并落盘一份全新的data/daemons/guild.o(测试完成后已 按惯例清理,不提交这份纯测试产物)。
测试覆盖:干净通过的部分
- 注册 + 建角:
TestHero(id/密码/性别/邮箱/真实姓名/推荐来源 六项资料全走完,未提前用回车跳过任何一项)→c建角选人类种族 →Aidan;账号存档确认落在data/links/t/testhero.o(不是data/players/——这个 mudlib 的登录账号和角色身体是两套分开的存档体系,前者由secure/user.lpc的save_me()写/data/links/,后者由角色 body 对象自己的save_me()写/data/players/,两者都验证过存在且内容 正确)。 - 移动/探索:Grand Hall(
domains/std/Wizroom.lpc)→ 通过enter portal(不是方向指令,是一个set_objects()里挂的可交互物体) 到domains/std/rooms/beach/Sandy_Beach(原版自带的"pirate"主题 演示解谜区,房间自身的告示牌明确写着"DON'T USE THIS AREA ON YOUR MUD!!! ... providing this area as an example only"——确认是有意的 示例内容而非未完成的真实新手区,用wade in ocean这类挂在物体上 的EXIT_OBJ方法而非方向指令才能继续深入,属于教学向导航技巧的 演示,未强行走完整个九房间解谜,仅验证了它的存在/可达/告示牌文案 与源码一致);goto(巫师传送指令,AUTO_WIZ下人人可用)验证可用,用于在 Grand Hall/Monster Room 间快速往返,不构成绕过测试的问题——纯粹是这个 lib 本来就把每个 角色都当巫师对待的既定设计。 - 战斗:
kill guard(Monster Room 的 5-max-health 门禁守卫)—— 真实回合制近战,命中/闪避/擒摔/致残("You cannot use your right arm anymore")描述文本随机生成、伤害正确累加到躯干/四肢分区 HP、skills面板确认 Combat/Defense/Melee/Unarmed 技能百分比确实随着战斗行为 自动增长("熟能生巧"机制,符合cmds/player/skills.lpc自己文档注释里的说明)。角色是巫师身份,重伤到"If you were mortal, you would now no longer be mortal"后自动免疫真死——符合 §10.7 checklist 里"用 wizard 身份角色测试战斗更安全"的既定经验。 - 公会/门禁交互:详见上面两处 bug 修复;修复后确认"没有对应公会 成员身份时被礼貌拒绝,而不是崩溃"这一行为在两个独立角色 (
Aidan/Rowan)上一致。 - NPC 对话:
talk to greeter——进入带编号选项的对话树(数字选 Hello!/What do you do?/Where do I get LIMA?/...),驱动侧数字菜单 分派正确。 quests:正确列出"pirate"/"Pirate"两个可见任务及步骤数/状态 (与 Sandy Beach 演示区的sand_with_treasure.lpc埋的QUEST_D->grant_points()调用对应)。quit→ 用户菜单 →q完全断开 → 真实等待 75 秒 → 重新连接:quit游戏内指令正确回到用户菜单并广播"has left Lima Mud";q从用户菜单彻底断开后ss -tn确认端口上无残留连接(干净断开,非半开连接);75 秒真实等待后 用s→p重新进入游戏,角色状态(位置 Grand Hall、属性、之前战斗 造成的创伤已自然恢复)全部正确保留,log/runtime行数在等待前后一致(无新增错误)。debug.log 在这次驱动的启动方式 下本来就是死的(AGENTS.md §10.9 已归档的通用现象——log directory : /log相对驱动启动时的 cwd 解析,chdir 前就 fopen 失败,此后整个进程生命周期都不会再重试);这个 lib 真正在用、也 确实工作正常的错误日志是work/log/runtime(配合errors频道),本轮两处 bug 的完整驱动栈跟踪都是从这个文件里读到的确认 证据,等待前后的行数比对也是拿它做的。- 管理员账号:
fluffos/Mud@2026重新登录验证,who显示Role: Admin,状态与此前"管理员账号播种"一节记录的验证结果 一致,未发现回归。
已知但未修的观察项(不确定是否算 bug,按惯例只记录不改)
guild_guard.lpc的默认拒绝文案"The $n $vpush $t back..."对set_name("guard")这类没有专有名词、走"a guard"泛指格式的对象会 拼出"The a guard pushes you back"这种双重冠词——纯粹的英语措辞小 瑕疵,不影响任何功能判定,本次未改动。- Sandy Beach 的九房间"pirate"演示解谜区本身明确标注"不要在你的 mud 上使用",本轮只验证了可达性/告示牌文案/
wade in ocean这一个非方向性出口的存在,未强行走完整个解谜链(含dig挖出宝箱的完整 8 次交互序列)——按 §10.7 checklist 第 6 条的原则,明确记录为"内容探索深度不足"而非"已知损坏"。
Upstream PR 审计(2026-08-31,standing queue 项目)
按 project_fluffos_org_upstream_pr_queue.md 的标准流程,把上文"编译
期发现的真实 bug"一节里已经在本地 work/ 修好的 6 处 bug 逐一核对
是否需要(也能否)提交回上游:
- bug #1(
cache_dir()等 4 处[0..<3]扩展名切片宽度):不提交 上游——这是本项目自己.c→.lpc改名触发的问题,上游原生文件名 仍是两字节的.c,[0..<3]在上游代码里本来就是对的。纯粹的 driver-build/转换集成问题,不是 lima 自己源码的 bug。 - bug #2(
set_droppable(int g)应为mixed g)、 bug #3(eval_dest()private→protected)、 bug #6(confirm_decision()的dec = "y"赋值/比较符打错): 三处都是 lima 自己源码里独立于任何编译期开关的真实逻辑 bug(和NO_LIGHT/NO_ADD_ACTION/NO_WIZARDS/OLD_ED/PACKAGE_UIDS这套本 lib 专属驱动 flag 完全无关,任何启用静态类型检查/严格private语义的驱动都会踩到)——判定为上游真正应该修的 mudlib bug。核对github.com/fluffos/lima(HEAD 仍是快照记录的dbcef2a,无新提交/PR/issue 涉及这三处)确认未被上游独立修复后, fork+clone 到 scratch 目录、对照上游原始.c文件逐字节确认 bug 存在,准备提交 PR 时发现fluffos/lima已于 2024-10-08 被官方 归档为只读仓库(gh pr create直接报 "Repository was archived so is read-only",无法开 PR)——meta.json/本文件开头记录的 "2026-08-24 克隆 HEAD dbcef2a" 只是 clone 时未受影响(归档仓库仍可 clone,只是不能收 PR/新 commit)。
- 核实同一 LIMA 社区已迁移到 github.com/limalib/lima(非
fluffos/lima 的 GitHub fork 关系,但同一 Discord #LIMA 频道、
同一品牌"limamudlib.dev",且本文件前面"附录(2026-08-26)"一节
已经做过一次代码血缘对比,确认是同一 LIMA 代码库的延续版本,非
独立重写)——直接 clone 核对,std/modules/m_gettable.c/
std/modules/m_exit.c/obj/usermenu/usermenu.c
三个文件对应位置的三处 bug 逐字节存在,尚未修复(master/
stable 两个分支都确认过),且该仓库尚无任何提及这三处的
open/closed PR。判定为这三处 fluffos-org 遗留 bug 的实际可提交
对象,改为向 limalib/lima 提交:
PR #49(set_droppable)、
PR #50(eval_dest
private→protected)、
PR #51(confirm_decision
赋值/比较符)。
- **bug #4(
ENSURE(x /* 注释 */)宏参数内联注释)、 bug #5(heredoc 开始标记同行接正文):均已在原文标注为fluffos/fluffos驱动**本身的 bug,不是fluffos/lima/limalib/lima的 mudlib 源码 bug——本次任务范围是审计 lima 这个 mudlib 是否需要提 PR,不包括驱动仓库层面的 PR,未提交。 - 交叉核对
libs/spacemud(limalib/spacemud,明确基于 LIMA 代码库 "as few changes to Lima itself as possible")与libs/wilderness(独立的 2000 年代 LIMA 快照)各自的NOTES.md:wilderness 独立 发现了完全相同的eval_destprivate/protected 问题(private protected双修饰符写法,同一文件同一函数)和set_droppable同类"声明类型比函数体/文档窄"问题(另外还多发现了set_destination/set_fluid_level/m_complex_exit.lpc的set_method/add_method等 4 处同类型 bug),以及完全相同的ENSURE宏注释驱动 bug——回头检查这 4 处 wilderness 独有的额外 实例在本 lib 当前work/目录里对应文件的声明均已经是正确的宽 类型(set_destination(mixed f)、set_fluid_level(int x, int y)、m_complex_exit.lpc的exit_messages/enter_messages已经是mixed *),说明这 4 处是 wilderness 那个独立 2000 年代快照自己的 历史遗留内容分支,并非 stockfluffos/lima/limalib/lima现存的 bug,不需要额外提交。spacemud 的NOTES.md未提及这三处 bug(其 onboarding 没有触发到这几个调用路径),无新增交叉发现。
Shop (2026-09-04 librarian slice)
Biff's General Store (/domains/std/Shop) is live. Seeded admin
fluffos / Mud@2026 (user menu p to enter; character already
selected) started with empty pockets (money → "only lint").
eval this_body()->add_money("gold", 50.0) credited 50 gold.
goto /domains/std/Shop, list showed Biff's stock: amber ale
and red apple at "no gold", dull sword at 1 platinum, red/blue
sticks at 2 copper. buy apple from biff completed but is a
zero-price food (query_value() from uneaten eats × heal, heal
is 0). buy sword from biff was a real paid sale: "You buy a
dull sword for 1 platinum. You give 10 gold to Biff the
Shopkeeper." Inventory: dull sword + red apple. money 50→40
gold (platinum factor 10). No programming bug.
拜师: N/A. This is a FluffOS reference/demo lib, not a
sect/family game. Live probes: apprentice / become / 拜师
→ "I don't know the verb"; join → parser "A valiant attempt";
help guild has no player topic (only wizard guild_d).
GUILD_D stock guilds are still not loaded (same as the
2026-08-27 guild_guard/sorcery finding). School rooms are
a wizard-coding tutorial, not an apprenticeship.
深度功能测试(§10.7,2026-10-04)— #pragma no_warnings removed (patches 0003–0006), every compile warning fixed
lib/include/mudlib.h (so every file that includes it) and the three example rooms lib/domains/std/room/{Attic,Church,
wiz_hall}.c open with #pragma no_warnings upstream. The catalog's playable tree must not carry it (AGENTS.md), so
patch 0003 removes the four lines and 0004–0006 fix what they hid. With the pragma gone the Lima-driver lpcc
(~/src/fluffos-lima/build-debug/src/lpcc) printed 549 distinct diagnostics over the 1174 objects; on the patched tree
there are 8, all errors in files that do not compile, and 0 warnings (lpcc --batch over every .c object of a
fresh upstream copy with the six patches applied: 1159 load, 15 do not).
The patches are split by directory so that a pin bump that breaks one still leaves the others applying: 0004 is
std/ (77 files), 0005 daemons/ secure/ obj/ trans/ WWW/ (53), 0006 cmds/ domains/ contrib/ (87). They were made in
a scratch repo (scripts/submodule_patch_scratch.py build lima --below 3, then the usual lpc_warnings.py /
lpc_name_winner.py / lpc_rename_ident.py / lpc_fix_unused_init.py passes on its .lpc-named copy, then
export lima --stage std-fix-compile-warnings:std/ ...); the same two commands regenerate them against a new pin.
rebase_upstreams.py holds a pin when a patch stops applying.
Root causes (KB 04 §6.10, the Lima-family classes again):
- Private names that met in one class got module prefixes:
base(exit_baseinm_exit,cx_base,obj_base/obj_default_desc,rx_baseinroom/exits),env(act_env,blocker_env),move_hook(blocker_move_hook,wander_move_hook,wield_move_hook),taste_action(drink_taste_action),fluid_disturb(drink_disturb),skill_used/magic_skill_used(spell_*inspell.c,throw_skill_used),stats(conv_stats),keywords(md_keywords),colours(frame_themesin the admtool frames),no_heals,queue, andtest_dummy'starget;guild_dlost a dead saveable duplicate ofguild_favours. - A mixin that inherits its own dependency:
m_assistancere-inheritedM_MESSAGES(whichADVERSARYhas) and the yakitori delivery boy inheritedM_ASSISTANCEtwice; the module declarestargetted_action()now. - About 150
F() inherited from both A and Boverlaps in 62 files are named in the leaf (lpc_name_winner.py). Where two realmudlib_setup()s met, the leaf now calls both:food+m_decay(hamburger, three skewers),object+light_object(candle),container+m_wearable(backpack),adversary+m_accountant(bank accountant),object+m_companion+m_stateful(dancing lights).std/body.ckeeps its wrapper block outside#ifdef USE_GUILDS, withnomaskmoved frombody/cmd.c'sdo_game_command()to the wrapper. weapon.cdroppedm_wieldable::internal_setup():m_durability'snomask internal_setup()won the overlap, sowielding_limbswas never registered for saving on any weapon, andspecial_longsword.ccould not compile at all ("Illegal to redefine 'nomask' function"). Thenomaskis gone andweapon.ccalls both;special_longswordloads and clones now (it names its four overlapping functions too).- Return-type disagreements:
base_room'sdo_listen()/do_smell()return 1 like the yakitori rooms' versions, the five transient effects definevoid do_effect(object affected)liketransient.c,xterm256_dandnavigationprototypes match their definitions;?:with a string and an int branch becameif/return; rest parameters arestring *x...;(: "pop_up", "fast forward" :)inrecorder.cwas a comma expression, a real pointer now;m_frame's default column colour is the string"<res>"(evaluate()returns it as is). - Not warnings, but broken enough to fix on the way:
m_react.c(text block terminator indented and followed by;,string *partsholding an int counter),Sloping_Tunnel(set_default_exit()is the retired API forset_default_error()), the yakitori uniform (ARMOR/set_armor_class()forARMOUR/set_armour_class()), the contrib boards (set_in_room_desc()took a string but is handed a function; it keepsevaluate()-able values now),contrib/bboardandcontrib/marriage/user_d(staticvariables),WWW/cgi/mudinfo(mixed array) andsecure/daemons/ftp_d(a$(local)inside the outer functional of the PASV socket).
Live on a pristine tree with the Lima driver, baseline (upstream with patches 0001/0002) against the patched tree, the same
48-command battery as the seeded fluffos admin: clones of the troll, goblin, dragon, wolf, test dummy, Harry, Robert,
four weapons, water, ale, keg, tankard, hamburger, candle, backpack, trainer, bank accountant, horse, camaro, cooking
station, delivery boy and uniform; wield / drink / eat / light; smell and listen in the kitchen; the cave and
the wizard room; help. The transcripts agree except for two objects that now load (special_longsword,
Sloping_Tunnel); the console shows no warning and no error. Pre-existing, not touched: fighter_master raises "Non-existant
guild - stock-fighter" in guild_d, and m_react's add_script() is upstream's own stub ("Don't you wish this function
wasn't missing").
Left: 8 diagnostics in the files that do not compile: WWW/cgi/autodoc.c (inline @END text blocks), contrib/marriage/
{finger,finger_d}.c (the class member spouse the contrib expects the core finger class to have),
domains/std/object/portable_board.c (BOARD_OB is defined by nothing in the tree) and std/behaviour/test.c (a
three-line scratch file); another eleven objects fail to load in a batch without a diagnostic (they need arguments or a
user: virtual rooms, menus, mailers, contrib/bboard/news_d).
- 2026-10-14: catalog patch
patches/0007-float-truncation.patch. It fixes the 43Float value truncated to intsites that the Lima-configured driver (~/src/fluffos-lima, rebuilt at FluffOS 970d9f09) reports, in 17 files.
- daemons/loot_d.lpc: count *= level / 16.0 converted the multiplier first, so below level 16 the loot count was 0. It is now count = to_int(count * (level / 16.0)).
- std/adversary/condition.lpc: the drunk stance penalty STANCE_IMPACT_AT_30 (1.2) went into an int and had no effect. It is now a float, and the skill test argument uses to_int().
- std/container.lpc receive_object() and std/fluid.lpc will_fit() kept float masses in ints, so an item under 1 weighed nothing in the capacity check. They now use float receivers.
- The rest spell their truncation to_int(), with the same values.
- Gameplay-visible: low-level loot drops, the drunk stance penalty, and container capacity for light items now work as designed.
- The 17 changed objects load before and after. The patch applies on its own on the pin and after 0001–0006.
深度功能测试(2026-10-21,play 扫描):以种子管理员 fluffos 跑 mortal 和 wizard 命令,clone 补了一条(patches/0008)
用 lima 专用驱动登录(PLAY_DRIVER=~/src/fluffos-lima/build-debug/src/driver;scripts/pristine_tree.sh 现在能把子模块工作树 + patches/ + overlay/ 搭成测试树,scripts/play_login.py 认得 [spcrwq] 菜单并选 p),scripts/play_sweep_lib.py lima --account fluffos:Mud@2026:mortal 命令 160 个(157 行),--wiz 再加 235 行,控制台只有两条。clone(wiz):没有参数又没有 cwf 时 new(arg[0]) 的参数是 0,FluffOS 报 Bad argument 1 to EFUN new() Expected: string, Got: int(0)(上游依赖旧驱动里 new(0) 答 0 再走 “Failed to load file.”);patches/0008-wiz-clone-without-a-file-name.patch 先检查 stringp(arg[0]),没有时打印 clone [filename],实测 0 条发现。treefor:是扫描喂给 eval 的垃圾输入编译出的警告(/tmp/EVAL.c),不是 lib 的问题。其余 needed-escape(分页器、编辑器、对话)和 1–2 条 stuck-reconnect 不是错误。
data/secure/ 没有人建:master 的 valid_write() / valid_read() 拒绝分支往 ACCESS_LOG 写日志(2026-10-22,KB 06 §7.358)
lib/secure/master.c 每次拒绝一个路径(valid_write() / valid_read())都往 ACCESS_LOG(/data/secure/LOG)写一行;干净的 git clone 里没有 data/secure/,拒绝分支里的 write_file() 就抛错。data/secure 已记进 scripts/wasm_keep_dirs.txt(lpc_keep_log_dirs.py --root=data;该扫描列出的另外 29 个 data/ 目录都读过了:留言板和彩票精灵在 save() / assure_file() 里自己建目录,passwordd 用 assure_file(),votebox 在巫师大厅,rwho_a 只在联网时写)。