Daniel's World

✅ 可玩

逸俠世界

yxsj

🔑 fluffos / Mud@2026 更新 66a3bd8 2026-10-28 源码 下载 ZIP

▶ 开始游玩 · Play Now

《逸俠之世界》(Daniel's World),ES II 引擎家族,台灣出品(BBS 140.127.179.161,聯絡信箱 [email protected]),是这批档案里少见的"校园"题材:新角色从现代校园场景起步(`d/school/`,教室、中庭花园、实验大楼、厨房、脚踏车停放处一应俱全),而不是常见的武馆或城镇,`d/Pili/` 还收录了台湾"霹雳布袋戏"相关的场景与 NPC,把布袋戏元素也编织进了武侠世界观。注册流程为英文 ID、中文名字、密码加确认、电子邮件、性别。和这批档案里的 `yxzsj` 是同一份代码库在不同时间点留下的两个版本——同样的标题、同样的联络信箱与档案结构,但并非完全相同的打包(例如 `combatd.lpc` 相差约 80 行),值得当作两份独立存档保留。

English

Daniel's World (逸俠之世界), part of the ES II engine family, produced in Taiwan (BBS 140.127.179.161; contact [email protected]). Unusually for this corpus, new characters start on a modern campus (classrooms, a courtyard garden, a lab building, a kitchen, a bike rack) rather than the usual wuxia inn or martial hall, and the world folds in scenes and NPCs from Taiwan's Pili Puppetry (霹靂布袋戲) glove-puppet franchise alongside the standard jianghu backdrop. Registration runs through an English ID, Chinese name, password with confirmation, email, and gender. Shares the same codebase, title, and contact email with this collection's yxzsj, an independent build from a different point in the same game's history (not a byte-identical repackaging).

README

内容亮点

重大编码问题(在任何 WASM 修复之前就先修好了)

这份档案是 BIG5 编码,但最初的 convert_lib.sh 转换(该脚本原 本写死用 GB18030 当来源编码)把每一个字串都用错误的编码表转 了一遍——结果是合法的 UTF-8,但每一句玩家看得到的讯息都变成了看起 来正常、实际上语意不通的乱码汉字(比如原本该是"您的英文"却变成 "眤璣ゅ"),而不是 iconv 直接报错,所以乍看之下像是转换成功了。用 两种编码分别解码同一份原始档案的 write("...") 字串比对后确认了 问题。已经把 scripts/convert_lib.sh 扩充成可以传第三个"来源编 码"参数(预设仍是 GB18030,不影响其它任何档案),用 BIG5 重跑 了这份档案的转换。由于 convert_lib.sh 会整个重建 work/,之前 已经套用在(乱码版)work 树上的 WASM 修复全部要在新转出来的 BIG5 版本上重做一遍。

本次修复的关键 bug

1. 经典 §8.1 GBK 字节区间 is_chinese()/check_legal_name() (奇偶校验、[i..<0] 后缀切片、字节数没减半的 2/12 上下限)—— 改成逐码点的 0x4e00-0x9fff 判断和 name[i..i],上下限减半 成 1/6。 2. §7.12 tell_room() 裸露 exclude 参数导致 message() 崩 溃——补上标准的 exclude || ({}) 保护。 3. master.lpc 的 valid_write()(valid_read() 本来就无条 件回传 1)补上了标准的 user == this_object() 短路判断。 4. §7.50 is_killing(object) 与 is_killing(string id) 类型不 匹配,修好了 9 处呼叫点(几个门派 roar/nuke 档案、 u/d/daniel/ 底下几个 NPC 档案、cmds/std/surrender.lpc)。 5. data/ 目录下 39 个存档档案是 CRLF 换行,统一转成了 LF。 6. 真正的(非 WASM 专属)bug:adm/daemons/securityd.lpc 的 valid_write() 在 save_object 快速通道里用 file == user->query_save_file() 做完全相等比对,但这台驱动 的 save_object() efun(vm/internal/base/object.cc)在呼叫 valid_write() 之前会先把 __SAVE_EXTENSION__(.o)接在档 名后面——完全相等比对永远不会成立,导致每一次玩家存档(注 册、进入游戏、退出)都被拒绝,报"Denied write permission in save_object()"。改成和 query_save_file() + __SAVE_EXTENSION__ 比对就修好了(这个正确写法在本次会话的 yhwhpublicfi/yxjh 两份 姊妹档案里本来就有,yxsj 这份只是漏掉了)。

adm/daemons/ftpd.lpc、adm/daemons/network/dns_master.lpc 都已 经在 adm/etc/preload 里被注释掉,仅有的呼叫者也都是巫师专用指 令,属于完全休眠,本次没有改动。

已知但未修复的问题

管理员账号 / Admin account

警告:对外公开架设前请务必修改此密码。

本地运行

cd libs/yxsj
~/src/fluffos/build-debug/src/driver config.fluffos

游戏端口:40167。

NOTES · 移植与修复记录

WASM 修复摘要(迁移自 meta.json 的 group_note)

逸俠之世界(Daniel's World),ES II 血统,台湾出品(BBS 140.127.179.161,联系人 [email protected])。在任何 WASM 专属工作之前,先发现并修复了一个重大编码 bug:这份档案原本是 BIG5 编码,但最初的 convert_lib.sh 转换(硬编码 GB18030 作为源编码)用错误的码页对每一个字符串做了往返转换——结果是一段合法的 UTF-8,能解码成真实存在但毫无意义的中文字符(比如 write("\u7729\u7420\u3085") 而不是 "\u60a8\u7684\u82f1\u6587"),而不是报出 iconv 错误,所以看起来像是转换成功了,但每一条面向玩家的信息其实都是乱码。已通过用两种编码分别解码同一份原始 .c 档案,对比一处 write() 字符串字面量来确认。给 scripts/convert_lib.sh 加了一个可选的第三参数"源编码"(默认 GB18030,其它所有档案不受影响),用 BIG5 重新跑了这份档案的转换——之前所有在(GB18030 乱码版)代码树上做的源码级 WASM 修复都必须在重新用 BIG5 转换出来的代码树上重做一遍,因为 convert_lib.sh 会先删除再从 raw/ 重新生成 work/。WASM 专属修复:(1)经典的 §8.1 GBK 字节区间 is_chinese()/check_legal_name()(奇偶门槛、[i..<0] 后缀切片、字节数翻倍的 2/12 界限)改成了逐码点 0x4e00-0x9fff 检查和 name[i..i],界限减半为 1/6。(2)§7.12 类的 tell_room() 裸 exclude 导致的 message() 崩溃,用标准的 exclude || ({}) 保护修复。(3)master.lpc 的 valid_write()(valid_read() 本来就无条件回传 1)加上了标准的 'user == this_object()' 短路判断。(4)§7.50 类的 is_killing(object) 对 is_killing(string id) 修复了 9 处呼叫点(kungfu 类的 roar/nuke 档案、几个 u/d/daniel/ 下的 NPC 档案、cmds/std/surrender.lpc)。(5)data/ 下 39 个 CRLF 换行的 .o 存档档案统一转换成 LF(restore_object() 在处理嵌套映射里的 \r 时可能会卡住)。(6)一个真正的 bug,不是 WASM 特有的:adm/daemons/securityd.lpc 的 valid_write() 对 save_object() 快速路径检查的是 'file == user->query_save_file()' 精确字符串相等,但这个驱动的 save_object() efun(vm/internal/base/object.cc)在呼叫 valid_write() 之前会先给文件名加上 __SAVE_EXTENSION__(.o)后缀——精确匹配永远失败,导致每一次玩家存档(注册、进入游戏世界、quit)都被拒绝,每次都抛出'Denied write permission in save_object()'。已改成对照 query_save_file() + __SAVE_EXTENSION__(和本次会话手足档案 yhwhpublicfi/yxjh 里本来就正确的写法一致——yxsj 的 securityd.lpc 只是漏掉了这一处)。adm/daemons/ftpd.lpc 和 adm/daemons/network/dns_master.lpc 都处于休眠状态(在 adm/etc/preload 里被注释掉,只有带保护的巫师指令呼叫者)——保持原样。已知的非阻断性问题,没有进一步深挖:adm/daemons/chinesed.lpc 和 adm/daemons/emoted.lpc 在启动时都会抛出一个被捕获、非致命的'Illegal mapping format while restoring dict/emote'(和另一份档案——yxjh 的 named.lpc——上见过的同一种无法解释的 restore_object() 失败签名一致,只是内容不同,所以很可能是驱动本身的共有怪癖,而不是某份档案特有的损坏;实际游玩中没有观察到任何功能降级)。注册/登录过程中会出现一次针对 obj/user 的 command_hook 的短暂'apply() with insufficient permission... needs: private, has: hidden',但不会阻挡指令处理、进入游戏世界、或 quit。管理员账号播种:fluffos (admin) 加入 adm/etc/wizlist((admin) 是这份档案的最高阶层)。注册流程在格式化前后都完整验证过(英文 id→y 确认→中文名字→密码+确认→电子邮件→性别→进入士林高中/校门区域),管理员权限已通过'目前權限﹕(admin)'确认。LPC 格式化工具对全部 949 个档案运行(写入 919 个,21 个转档之前就存在的错误,9 个未改动)。没有 :: 父类呼叫拆分命中,没有 case 标签带尾随注释的候选,没有 CJK 重新加空格/转义损坏命中。

§7.86 跨库扫描修复(留言板 post 崩溃)

§7.100 sweep (2026-08-19)

Fixed the corpus-wide inherit ROOM; ... replace_program(ROOM); redundant-replace bug (AGENTS.md §7.100). 185 live occurrences deleted: 184 via scripted sweep (fix_710_room.py), plus 1 hand-fixed roommaker-tool template (obj/roommaker.lpc, simple string-builder variant, byte-identical sibling of yxzsj). 2 already-commented-out instances left untouched. No real .lpc source found under work/data/. Verified via build-debug driver boot: clean compile, zero new "cannot replace"/"cannot bind" debug.log lines; confirmed serving via raw-socket connect on port 40167.

§7.30 uninitialized-mapping accessor sweep (2026-08-20)

Corpus-wide mechanical sweep of the feature/skill.lpc shared-lineage bug (confirmed independently on xiakexing2017/jqxz2015/haiyang2 via round-four testing): 3 accessor(s) in this file returned a raw never-initialized mapping instance variable (defaults to int 0, not ([]), until first assigned), crashing any unguarded keys()/sizeof()/indexing caller for a fresh/untrained character. Fixed at the accessor level (mapp(x) ? x : ([])) per the documented remedy. Verified via lpcc --batch static compile check only (not a live boot) as part of a large mechanical sweep; not individually functionally re-tested live on this lib.

深度功能测试(2026-08-27,round-two,AGENTS.md §10.7)

单一连续会话,~/src/fluffos/build-debug/src/driver config.fluffos 原生驱动,Python 原始 socket 脚本交互。管理员账号 fluffos / Mud@2026(此前只在 wizlist 里播种了名字,从未真正注册过存档——本次 补做了真实注册,确认 目前權限﹕(admin) 生效)。测试角色:注册英文 id → y 确认 → 中文名 → 密码+确认 → email → 性别 → 进入 /d/school/gate (逸俠中學校門口,士林高中场景,非 help newbie 文档描述的雪亭鎮/ 天邪派等旧版地理——那份 newbie 文档看起来是这套 ES2 引擎的通用模板文字, 和这份档案实际拥有的「学校」主题内容不是同一批地图;本档案真正的门派 体系是把「逸俠中學」本身当作 family/师门,教师 NPC(吴明德、林泽贤、 何景国等)就是可拜师学艺的对象,apprentice/learn 都走这条路径)。

发现并修复 4 个真实 programming bug,1 个已确认的 §7.126 类实例记录在案未改(低优先级,见下):

1. chinesed.lpc 字典存档格式损坏,learn/skills 等核心指令必崩 (新发现的 bug 形状,会话中新增 AGENTS.md 条目):data/chinese.o (to_chinese() simul_efun 依赖的技能名中译字典)在原始 1995 年 BIG5 存档里,凡是遇到第二字节恰好是 0x5C(ASCII 反斜线)的中文字 (如「功」=0xA5 0x5C、「许」=0xB3 0x5C),当年经典驱动的 字节级 save_object() 会把这个 0x5C 当成需要转义的字符,写成 0x5C 0x5C(如 raw 档案里「玄子神功」结尾是 ...功\\")——这在 字节层面能被同样字节级的 restore_object() 正确还原。但本项目 convert_lib.sh 的 BIG5→UTF-8 转换是纯字节流转码,不理解这层转义 语义,把 0x5C 0x5C 逐字节转成了两个字面反斜线字符,其中一个紧贴 着字符串的收尾引号——对最后一个词条「玄子神功」而言,功\" 被 LPC 编译器解析成「转义引号」而不是「收尾引号」,导致整个映射字面量 永远读不到真正的收尾 ]),restore_object() 抛出 *Illegal mapping format while restoring dict.(此前 NOTES 曾把 这条启动期警告当成"已知非阻断性问题,实际游玩未观察到功能降级", 这个结论是错的——restore() 失败时 dict 变量被清成裸 0 而不是 ([]),任何真正调用到 chinese()(str.chinese("...")索引)的 地方都会崩溃:实测 learn <技能> from <人> 和 skills 两个核心 指令 100% 复现 執行時段錯誤﹕*Value being indexed is zero.(chinesed.lpc:83, 来自 cmds/std/learn.lpc/cmds/usr/skills.lpc 对 to_chinese() 的调用)。修复:写了一次性转换脚本,正确还原原始转义语义—— 按字节扫描找到引号定界的字符串字面量,字节级反转义(\\→单字节 \),把还原出的原始字节流按 BIG5 解码,再按 LPC 语法重新转义输出 为 UTF-8——而不是简单地对整份文件做一次天真的 BIG5→UTF-8 直接转码。 已验证:修复后 learn unarmed from teacher 正确显示"撲擊格鬥之技" 并成功涨点,skills 正确列出已学技能,无崩溃;chinesed.lpc 的 启动期 Illegal mapping format 警告也随之消失。同样的「BIG5 第二字节=0x5C 导致转换后多出一个反斜线」现象在若干真实 .lpc 源码字符串字面量里也存在(如 cmds/usr/quit.lpc、 adm/daemons/combatd.lpc、std/char/master.lpc 等,全档案粗略 统计约 54 个文件、129+ 处「功\」/「许\」之类的残留),但这些都出现 在字符串中段而不是收尾引号前,只产生一条无害的 warning: Unknown escape sequence 编译警告,尚未观察到会造成编译 失败或崩溃的位置——评估为内容质量瑕疵(显示文字里可能多一个游离的 反斜线字符)而非阻断性 bug,本轮未做全量修复,值得未来专门开一次 转换补丁扫描(新增 AGENTS.md §4.4 记录这个模式,供其他 BIG5 血统档案参考)。 2. log_error() 把编译 WARNING 当成 ERROR 广播给玩家(AGENTS.md §7.103 已知模式的新确认实例):adm/obj/master.lpc 的 log_error() 对 this_player(1) 无条件 efun::write() 每一条编译 诊断,包括纯警告(Unused local variable、Unknown escape sequence 等)。实测:一次全新注册+登录流程里,仅仅是首次懒编译 /std/char.lpc 及其继承链,就在管理员屏幕上刷出十几条 編譯時段錯誤﹕...warning: ... 噪音。修复:按 §7.103 文档化的 remedy 加上 strsrch(message, "warning:") == -1 门槛,真错误仍然 照常显示,纯警告不再回显给玩家(依然写入 log)。已验证:同样的 注册流程修复后干净无噪音。 3. cmds/wiz/call.lpc(以及 master.lpc/purge.lpc/ securityd.lpc)对玩家对象发起 call 时崩溃 —— static→nosave 转换 sed 误伤了字符串字面量(AGENTS.md §4.3 已知碰撞类型的新确认 实例):管理员对另一在线玩家执行 call <玩家>->query(...) 时 100% 复现 執行時段錯誤﹕*Wrong permissions for opening file /log/nosave/CALL_PLAYER for append. "No such file or directory"。 核对 raw 档案发现:cmds/wiz/call.lpc、adm/obj/master.lpc(崩溃 日志 CRASHES)、cmds/arch/purge.lpc(PURGE)、 adm/daemons/securityd.lpc(promotion)这 4 个文件的 log_file 调用原本都是 "static/XXX",本项目的全局 static→nosave 保留字替换 sed 把这些字符串字面量也误改成了 "nosave/XXX"—— 而 work/log/static/ 目录下恰好还留着从 raw 档案转换过来的、 真实的 2000 年历史数据(CALL_PLAYER、CRASHES、promotion 三个 文件都有真实的巫师升级记录/崩溃记录/管理员通话记录),nosave/ 目录则从未被创建过,导致这四处 log_file() 调用全部会在实际触发时 崩溃,并让这些历史档案在功能上被架空。修复:把这 4 个文件的 5 处 log_file("nosave/...") 全部改回 "static/...",恢复到原始 raw 档案的字面量,重新指向真实存在的历史日志。另外给共享的 adm/simul_efun/file.lpc 的 log_file() simul_efun 本体加了 assure_file(LOG_DIR + file)(沿用 feature/save.lpc 里 save() 已经用到的同一个 helper)作为额外的防御性修复——以后即使 再出现引用了从未创建过目录的 log_file() 调用,也不会再崩溃, 而是自动建目录。已验证:管理员对在线测试角色 call ceshier->query("id") 修复前 100% 崩溃,修复后正常返回结果,且 写入的正是 work/log/static/CALL_PLAYER(历史数据依旧完整,新记 录追加在后面)。 4. obj/board/wizard_j.lpc:inherit "/std/jboard" + 多余 replace_program("/std/jboard")(AGENTS.md §7.86/§7.100 已知模式 的新确认实例,本次跨库扫描当年用宏名 BULLETIN_BOARD/ROOM 匹配,漏掉了这处用字面量路径 "/std/jboard" 的写法):这是巫师 专用的「工作进度报告板」(/d/wiz/jobroom),project/report 两个指令都会对 this_player() 发起 this_player()->edit((: done_describe_project/done_report, ... :)) 这种自绑定 lfun 闭包,命中和 §7.86 完全一致的 *cannot bind an lfun fp to an object with a pending replace_program() 崩溃。修复:删掉多余的 replace_program("/std/jboard");,保留 inherit。已验证:修复前 project <标题> 会话中未触发(该指令本身在崩溃修好之前从未真正 测试过),修复后 project 测试标题 正常打开行编辑器、"新工作計畫 提出"确认成功;顺带在同一个文件里发现并修复了下面第 5 条一个独立 的逻辑 bug。 5. std/jboard.lpc 的 do_read():read new/read next 永远失败 (独立发现的新 bug,随第 4 条一起在同一文件修复):"new"/"next" 分支算出 num 后没有 return,也没有用 else if 接上后面 if (!sscanf(arg, "%d", num)) return notify_fail(...) 这一段——于是又用同一个非数字的 arg("new")重新 sscanf 了一次,必然失败,把已经算好的 num 覆盖掉,永远落到 "你要讀第幾個計畫的簡報﹖" 这条错误提示,从未真正显示新报告。 对照同一血统的 std/bboard.lpc(普通留言板)的等价逻辑,确认那边 是正确的 if / else if / else if 三段链——jboard.lpc 是从 bboard.lpc 改造时漏掉了这个 else。修复:把 }\n if (!sscanf(...)) 改成 } else if (!sscanf(...))。已验证: 修复前 read new 返回错误提示;修复后正确显示新发的工作计划内容。

已确认但未修复的低优先级实例(§7.126 类,存档数据里的陈旧 .c 扩展名):9 个巫师账号(test/pill/chara/daniel/testa/ mous/testb/fva/eeman,均为原始档案自带的历史巫师,非本次 播种账号)的存档里都有一个 cwf("current working file",供 clone/update 免参数时使用的"上次编辑文件"书签)字段,值仍然是 convert_lib.sh 转换前的 xxx.c 路径,.c→.lpc 改名后这个书签 指向的文件已不存在,clone/update 空参数调用会静默失败(不是 崩溃,只是"找不到档案")。范围窄(只影响这几个多年不上线的历史 巫师本人的编辑便利,不影响任何玩家可见内容),且部分书签还同时带着 独立的大小写路径问题(如 /d/pili/glass/... vs 实际的 /d/Pili/glass/...),修起来需要额外核对每条路径,本轮未处理, 留档记录。

回归测试覆盖:注册全流程(英文 id→确认→中文名→密码×2→email→ 性别→进世界,管理员与普通角色各测一遍);师门加入 apprentice/learn/skills/score 全链路(此前因 bug 1 完全无法 测试,修复后确认可用);fight(安全切磋机制,不会真的杀死,确认 unconscious()→disable_player()→call_out("revive",...) 30~129 秒后自动恢复的机制存在且被正确触发);list/buy(福利社商店, 余额不足的拒绝路径正常,无崩溃;未实际获得充足游戏币走通"购买成功" 分支,因为这是纯粹的经济数值问题不在本轮抓 bug 范围内);两轮 "网络异常断线(不发 quit,直接断 socket)→ 重新连线" 循环——两次都 正确显示"重新連線完畢",ulist 确认全程只有一个 ceshier 对象 (/obj/user#202),断线重连后管理员 tell ceshier ... 依然能找到人(find_living/set_living_name 注册在 obj/user.lpc 的 reconnect() 里没有被清除,不是 AGENTS.md §7.152 那种"重连后 再也找不到人"的形状,此档案在这点上是干净的);quit 后核对 log/debug.log 干净无异常;管理员账号自举机制核对(此档案 没有"自动提升第一个注册者为管理员"的机制——fluffos 完全靠预先写入 adm/etc/wizlist 生效,属于 AGENTS.md §7.149 场景之外的干净设计)。 所有测试用临时角色(ceshiyixia/ceshier/ceshisan)已在提交前 删除存档,只保留播种的 fluffos 管理员账号(干净状态,无战斗损伤、 无测试道具)。

深度功能测试(§10.7,2026-10-04)— negative range ends

scripts/lpc_fix_negative_ranges.py: 5 line(s) in 2 file(s) count a range from the end with < (x[a..-1] -> x[a..<1], x[-2..] -> x[<2..]; KB 06 §7.209). In this driver a negative constant end gave "" / ({ }) and a negative start the whole value, so each of these returned the wrong slice; the compile warning A negative constant as the second element of arr[x..y] is gone from the files that compile. Range lvalues (s[0..-1] = text, the prepend idiom) are left alone. A paired compile of the changed files at HEAD and in the working tree showed no new error.

深度功能测试(§10.7,2026-10-04)— stray backslash before a closing quote

A Big5 character whose second byte is 0x5C was written with one extra backslash after it (the old driver read 5C 5C as one backslash and so completed the character) and the UTF-8 conversion kept it (KB 03 §4.4). Before a closing quote it escapes the quote, the string runs into the next line and the file does not compile. scripts/lpc_fix_big5_quote_backslash.py deletes it when the character before it encodes to cp950 with 0x5C as second byte and the quote ends an expression. Here: cmds/std/enable.lpc ("force": "…功\", and "dodge"), so the enable command never loaded, and four if (temp == "…功\") lines in daemon/class/sage/{su-sk,bullets}/bullets.lpc, u/d/daniel/temp/bullets.lpc and u/d/daniel/npc/mastersu.lpc. enable.lpc loads now. The four bullets files still do not: their first message line has 功\\" (two backslashes) in front of a colour macro, which this fix does not touch; read them by hand.

深度功能测试(§10.7,2026-10-04)— paths named in another case

The archive comes from a case-insensitive host: files are stored as BAGUA0.lpc, longjing.C, d/SHAOLIN/, the code names them in lower case (__DIR__ "bagua0", "tea/longjing", "/d/shaolin/..."), so the rooms, NPCs and items behind them could not be loaded (a .C file is not an .lpc file; KB 03 §4.5). scripts/lpc_case_paths.py --convert --apply yxsj: 66 file(s) renamed to lower case (.C -> .lpc, .H -> .h) and 1 path reference(s) in 1 file(s) rewritten to the new spelling; 0 skipped, 0 ambiguous. Load check of the 67 objects that were renamed, edited or name a renamed path (HEAD against the working tree, an object is the same under any spelling of its path): 1 PASS -> 67 PASS, 0 that loaded before fail now, 66 more load.

66 files moved (66 .lpc); mostly under d/Pili/glass (64), d/Pili/obj (2). A rescan with scripts/lpc_warnings.py yxsj is still due: the renamed files are compiled for the first time.

深度功能测试(§10.7,2026-10-05)— room base survives a raising NPC

scripts/lpc_resilient_room.py --apply: make_inventory() of std/room.lpc loads each inventory object as if (catch(ob = new(file)) || !objectp(ob)) return 0;. new() of a file that is not there returns 0 (the room just lacks that NPC), but new() of a file whose create() raises (a skill file for set_skill() this archive never had, a carry_object() item that is not there, a header that never shipped) raised inside the room and the room did not load at all. The wrong-case renames of this lib (the section above) make the NPCs behind them reachable for the first time, so a room that holds one with such a content gap would have gone from "NPC missing" to "room missing"; now it loads without that NPC, as it did before. The room base compiles and loads with the change (HEAD against the working tree). KB 03 §4.5.

深度功能测试(§10.7,2026-10-05)— tail() simul_efun

scripts/lpc_add_tail.py --apply: cmds/wiz/tail.lpc calls tail(file), an efun of the MudOS this archive ran on that FluffOS does not have (Undefined function tail: the command did not compile). adm/simul_efun/file.lpc now defines void tail(string file): the last 10 lines of the file, written to the player. Load check (HEAD against the working tree): 1 of the 1 tail command file(s) failed to load at HEAD, all load now, and the simul_efun file loads. KB 04 §6.2.

深度功能测试(§10.7,2026-10-05)— 房间列出的物件缺档时房间照常加载

房间基类 std/room.lpc:reset() 的 case 1: 拿到 0 就跳过这一项(if (!objectp(ob[list[i]])) break;)。档案里没有的 NPC 或物品(或 create() 出错的物件)原先会让列出它的房间整个加载失败(Bad argument 1 to environment(),或对 0 的 call_other()),连带这个房间后面的区域;现在房间照常加载,只是少了那个物件,下次 reset() 再试(KB 05 §7.25;scripts/lpc_resilient_room.py、scripts/lpc_room_reset_guard.py)。 验证:探针(scripts/lpc_room_reset_probe.py:房间基类列出一个不存在的物件,再调用 reset())HEAD 单件 崩溃、多件 OK,工作树 单件 OK、多件 OK。

深度功能测试(§10.7,2026-10-08)— 编译警告整理

scripts/lpc_warnings.py yxsj --fix 加 scripts/lpc_fix_no_effect.py --apply,共改动 161 个 .lpc(多为删去未用的局部变量)。HEAD 与工作树分别加载(新进程):144 -> 144 通过,无回退。启动时警告 12 -> 0;新进程登录(输入名字)正常。

编譯警告收尾(2026-10-13)

scripts/toolkit_fix.sh 一轮后剩三条,手工清零。改动的对象在 HEAD 与工作树各载入一次,regress 0(/adm/daemons/ftpd 两边都 PASS;u/d/daniel/npc/chinmo.lpc 两边都因 set_skill() 设了本库没有的武功而载入失败,是内容缺口)。

表情档读不出来(2026-10-15,KB 03 §4.4)

data/emoted.o 中 grin(邪惡的笑)一条在原档里就坏了:"target_res"$P指著自己。說道﹕就是我﹗","others_target":"$N指著$n。",一个键丢了后半截,后面接的是别的表情的文字;restore_object() 报 Illegal mapping format,整个表情系统一个表情都没有。按本条自己的字段补回:target_rest 取 myself_rest 把 $P 换成 $N(同档 groan 等表情都是这样),others_target 取 target_self 把 $T 换成 $n(同系各库 grin 的 others_target 就是这句)。丢失的那个表情无法恢复。驱动核对能读回。

NPC 带着本库没有的物品,载入失败(2026-10-15,KB 06 §7.73)

1 个文件、2 处在 create() 里 carry_object("<本库没有的文件>")->wear()(或 wield()、set_amount()):carry_object() 得 0,接着的调用报 Bad argument 1 to EFUN call_other(),NPC 根本载入不了,该有它的房间里就少了这个人。多是从别的系统搬来的 NPC 用了原系统的物品路径(最常见:/u/e/eeman/eq/cloth、/u/e/eeman/weapon/sword)。每处改为 { object carried_ob = carry_object(...); if (carried_ob) carried_ob->wear(); }(scripts/lpc_guard_carry_object.py):NPC 照常出现,只是不带这件东西;文件补回来(或是虚拟对象)照样会带上。载入检查:改动的文件改前能载入 0 个,改后 0 个(0 个原来载入不了),没有退步;其余 1 个另有原因(多为 set_skill() 了本库没有的武功)。

去掉的预定义宏 SAVE_EXTENSION(2026-10-18,KB 04 §6.11)

老版 MudOS 给每个 LPC 对象预先定义了 SAVE_EXTENSION(".o");FluffOS 的 ChangeLog(COMPAT_BUSTER 一条)把它删了,改用 __SAVE_EXTENSION__。本库没有自己 #define 这个宏,用到它的文件一律 Undefined variable 'SAVE_EXTENSION',整个对象载入不了。改成 __SAVE_EXTENSION__(scripts/lpc_fix_predefined_macro.py):adm/daemons/network/netmail.lpc、include/net/ftpdsupp.h、obj/token.lpc。token 是帮派领袖信物;netmail 是网络邮件守护进程(本库缺 mailer.h 等头文件,改后仍载入不了)。改后用 lpcc 逐个载入这些对象:1 个通过,1 个还有别的错误(adm/daemons/network/netmail:/adm/daemons/network/netmail.lpc:17: error: Cannot #include mailer.h)。

set_skill() 遇到本库没有文件的武功不再抛错(2026-10-18,KB 06 §7.256)

feature/skill.lpc 里的 set_skill()、map_skill()、prepare_skill() 碰到没有武功文件的名字就 error("F_SKILL: No such skill (…)"),调用它们的 NPC create() 整个中断:NPC 载入不了,刷出它的房间也少一个人。本库有 2 个对象设了本库没有文件的武功(2 个名字,最多的是 iceforce、icekill)。三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。抽样载入这些对象:2 个里 2 个通过。(scripts/lpc_guard_set_skill.py)

删掉 exert_function(整数)(2026-10-19,KB 06 §7.266)

/d/school/npc/healtech.lpc create() 里的 exert_function(10); 把整数传给了要字符串的直接调用(让 NPC 闲聊时自己运功),驱动报 Bad type for argument 1 of exert_function ( string vs int ),整个 NPC 载入不了。这是存档里的笔误,不知道作者想传哪个字符串,删掉这一句(只影响闲聊时的运功表现)。chidi 的同一个 NPC 早先就是这样处理的。

BIG5 的 0x5C 尾字节(扩展)(2026-10-19,KB 06 §7.266)

BIG5 里 功 等字的第二个字节正好是 0x5C(反斜杠),作者在它后面多写了一个 \,转 UTF-8 后这个多出来的 \ 跑到了收尾引号前面,把引号转义掉,字符串一直吃到文件后面。scripts/lpc_fix_big5_quote_backslash.py 原先只在引号后面跟 , ) ; } ] + 或行尾时才删;这次把“引号后面紧跟颜色宏再接字符串”(HIY"功\"HIR"』")也算进去,删掉多余的 \。u/d/daniel/npc/mastersu.lpc、daemon/class/sage/bullets/bullets.lpc 等 4 个文件,共 21 处。

RANK_D 对 0 的称呼(2026-10-19,KB 06 §7.273)

adm/daemons/rankd.lpc 的 query_respect()、query_rude()、query_self()、query_self_rude() 里 4 个函数开头补上 if (!objectp(ob)) ob = this_object();。NPC 在 create() 里写 "…" + RANK_D->query_respect(this_player()) + "…"(inquiry 对话、chat_msg),驱动自己跑 reset() 时没有人在场,this_player() 是 0。旧驱动里 0->query("age") 返回 0,称呼走默认分支;新驱动直接报 Bad argument 1 to EFUN call_other(),房间的 make_inventory() 吞掉错误,被打死的 NPC 再也刷新不回来。用 scripts/lpc_reach_census.py --walk 找到(xkx100 的武当弟子、一灯、汴梁书生…);修在守护程序里,不动 1,800 个 NPC 文件。

标准整理轮,第二轮(2026-10-19,scripts/toolkit_fix.sh)

本库在新驱动下重扫,机械修复:2 个未使用的局部变量,14 处浮点数赋给 int 的地方改成 to_int()。修后与 HEAD 逐个物件对比载入,没有退步。

call_out_info() 的每一项只有 3 个元素(2026-10-20,KB 06 §7.287)

驱动返回的是 ({ 物件, 函数, 延迟 }),不带 call_out 的参数,旧代码读第 4 项 info[i][3](callouts 指令、calloutd、cod 工具)一读就报 Array index out of bounds,指令从第一行起什么也打印不出来。改为 (sizeof(info[i]) > 3 ? info[i][3] : 0)(scripts/lpc_fix_callout_info.py)。改动:/cmds/arch/callouts.lpc(1 处)。

登录密码超过 255 字节:crypt() 抛错(2026-10-20,KB 06 §7.294)

驱动的 crypt() 对超过 255 字节的密钥直接报错(Error in crypt(), check your salt),老驱动则按任意长度散列。在登录提示符下输入 256 字节以上的密码,密码检查那一行就抛错,连线停在一个裸的 > 提示符上,登录走不完(password 指令的旧密码检查同样);用 300 个 x 当参数的扫描发现的。/adm/obj/simul_efun.lpc 里补了一个同名的 simul_efun crypt(key, salt):只有超长的密钥才被截短(先截到 255 个字符,再逐字符截到 sizeof(to_buffer(key)) <= 255,限制按字节算,一个汉字占三个字节),再交给 efun::crypt();普通密码原样散列,已存的密码散列不受影响(scripts/lpc_add_sefun.py crypt)。

sefun cat():缺文件时 write(0) 报错(2026-10-20,KB 06 §7.297)

void cat(string file) { write(read_file(file)); }:文件不存在(压缩包没带的欢迎/公告文件,或只有运行中的 mud 才会写的日志)时 read_file() 回 0,write(0) 在本驱动里报 Bad argument 1 to receive(),调用者(登录对话、cat 指令)被中断;老驱动什么也不打印。改成 write(read_file(file) || "");(scripts/lpc_fix_cat_unchecked.py)。改动:/adm/simul_efun/file.lpc。

翻页器 start_more(read_file(...)):文件缺失时 explode(0)(2026-10-20,KB 06 §7.300)

东方故事系的翻页器 void start_more(string msg) { ... explode(msg, "\n") ... } 被这样调用:start_more(read_file(HELP_DIR + "help/topics"))、start_more(read_file(NEWS))。压缩包没带的文件使 read_file() 回 0,explode(0, ...) 在本驱动里报 Bad argument 1 to explode()(不带参数的 help 指令就是这样出错的),老驱动把 0 当空文本。在 start_more 开头加了 if (!stringp(msg)) return;(scripts/lpc_fix_start_more.py)。改动:/feature/more.lpc。

NPC 的 chat() 在 chat_msg 为空数组时下标越界(2026-10-21,KB 06 §7.334)

NPC 基类的 chat() 是 if (arrayp(msg = query(...))) { if (random(100) < chance) { rnd = random(sizeof(msg)); if (stringp(msg[rnd])) ...,不检查列表是否为空。本库有 1 个 NPC 文件写成 set("chat_msg", ({ }))(或 chat_msg_combat)并带正的 chat_chance(例如 /daemon/class/ronin/master.lpc),每次心跳有 chat_chance% 的概率 random(0) 为 0、msg[0] 报 Array index out of bounds(驱动捕获,心跳继续,但每次一条运行时错误),这个 NPC 在内存里就一直发生。在 random(100) < chance 前面加 sizeof(msg) &&(scripts/lpc_fix_chat_empty.py;if (random(100) > chance) return 0; 的写法是 !sizeof(msg) ||)。改动:/std/char/npc.lpc(共 1 个文件)。

巫师命令不带参数时对 0 调用 efun(2026-10-21,KB 06 §7.336)

巫师(管理员)把命令敲成不带参数时,驱动把 0 传给字符串参数;下面的命令第一句就把这个 0 交给 efun,运行时报 Bad argument 1 to ...,这条指令从来没有用过。用 scripts/lpcc_bare.py 不登录、在一次 lpcc 求值里以 0 调用 main() 逐个确认后,由 scripts/lpc_fix_bare_arg.py 在第一处使用参数的语句前加守卫:文件自带 int help(object me) 的,守卫是 if (!arg) return help(me);(help() 定义在 main() 之后的 1 个文件补了原型 int help(object me);,否则编译报 Undefined function help)。改动(1 个文件,括号里是接住 0 的 efun):/cmds/wiz/test.lpc(present)。加载检查 HEAD 与工作树对照,没有退步。

(: 函数 :) 指针指向对象自己没有定义的函数(2026-10-22,KB 06 §7.355)

item_desc / inquiry / chat_msg_combat 里的 (: 函数名 :) 指向本对象(含包含文件、继承的父类)没有函数体的函数:编译不报错,用到时驱动报 *Undefined lfun pointer called: 函数名(look 某物、ask NPC about 话题 直接出错,战斗 / 闲聊里的指针在 NPC 的 chat() 里出错,这一拍心跳的后半段被跳过)。scripts/lpc_scan_callout_targets.py --kinds=lfun。 另有 1 处函数体被作者整段注释掉(指针没跟着删),是有意关掉的,没动。

(: 函数名 :) 指针指向本库没有的函数:只含这个指针的行加了注释(2026-10-22,KB 06 §7.355 补充)

inquiry / item_desc / chat_msg* 里的 (: 函数名 :) 指向本对象(含包含文件、继承的父类)没有函数体的函数:用到时(ask、look、NPC 闲聊)驱动抛 *Undefined lfun pointer called: 函数名,这一拍心跳的后半段被跳过。这些函数没有可以补回的来源(别的库同一路径的文件里没有,或者它还要用本库没有的东西),所以 1 个文件里 2 行只含这个指针的行("剃度": (: ask_for_join :), "出家": (: ask_for_join :),)加了注释,原文保留并写明原因,这一项当作没有这个话题 / 物品 / 闲聊条目(scripts/lpc_fix_dangling_lfun.py;#include 的文件在库里缺失的对象、指针不在整行上的写法没动)。 前提(function_exists())和加载检查(HEAD 与工作区对比,本库改动的 .lpc 文件)的结果见 KB 06 §7.355:没有一个回归。

汉字按字节切片的老写法没有换成按字符:改法名 "澄" + name[2..3]、辈分判断 name[0..1] == "澄" 等 1 处(2026-10-22,KB 06 §7.360)

原档按字节算字符串区间(一个汉字 2 字节):name[2..3] 是名字的第二个字,name[0..1] == "澄" 问名字是不是以「澄」开头。转成 UTF-8 之后,这个驱动的区间和 strlen() 都按字符算(strlen("澄观") 是 2,"澄观"[0..1] 是两个字),老算术原样留着、意思全变:拜入少林 / 武当 / 峨嵋等派的玩家被改成错误的法名(二字名 张三 变成一个字的 澄,更长的名字取到第三个字)。

本库 1 处(1 个文件):拼名字的赋值 1 处。例:name[0..1] → name[0..0](/daemon/class/bonze/master.lpc:99)。scripts/lpc_fix_gbk_slice.py 把下标按字符重算(社区的 UTF-8 版 xkx100utf8 里就是同样的写法,作对照);没有改的:截断(name = name[0..7],现在最多 8 个字,只是放宽)、ASCII 的区间(fname[0..3] == "room"、ctime(time())[4..19])和上下文看不出是汉字的区间。证明(scripts/lpcc_eval.py,bixiecanyang;其余各库是同一份文本):让澄字辈掌门 attempt_apprentice() 一个名叫「张三」的访客,HEAD 的法名是 慧,改后是 慧三;抽样 28 个库的 HEAD / 工作树加载对比 0 退步。工具不幂等([0..3] 折半得 [0..1],又是对齐区间),改过的文件记在 scripts/gbk_slice_converted.txt,不要重跑。

血条 / 表情标题按字节算显示宽度(2026-10-23,KB 06 §7.365)

老档把汉字当两字节:bar_string[0..(val * N / max) * 2 - 1] 把「格数」换成字节下标,半格血条在本驱动(按字符算区间)上会整条画满;巫师表情 edemote 的 repeat_string("─", 13 - (strlen(emotion) / 2)) 也会少减汉字宽度。scripts/lpc_fix_display_pad.py:去掉 CJK 血条 / 空白条上的 * 2(jings/qis 赋值和 blank_string3[0..(23-a)] 的 23→11 一并改),表情标题去掉 / 2;ASCII 空格的 none_string / empty_string 仍保留 * 2(一格两个空格)。本库 2 处(1 个文件)。证明(lpcc_eval.py hhsj):tribar_graph(6,9,12) 在 HEAD 画出整条 12 个■,改后为 6 个■+3 个□;麻将 strlen(tempB)/2 是两字符牌码,未改。

深度功能测试(2026-10-27,§6.10 未知 pragma save_binary/optimize)

删除本库中 FluffOS 不认的 #pragma save_binary / #pragma optimize… 行。