The Bird-Man World

✅ 可玩

鳥人世界 (The Bird-Man World)

niaoren

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

▶ 开始游玩 · Play Now

以金庸武侠小说为背景设定的繁体中文 MUD(作者原话:"这里的故事和情节,谜语的基本依据是金庸的十五本小说"),登录横幅标注底层为 `MudOS v22c2` / `MudOs V22pre11`,界面带有彩色 ASCII 艺术标题。玩家从"龙门客栈"起步,在各大门派、城市间闯荡江湖,修习内功、外功与各类技能,是传统武侠 MUD 的属性、门派、师徒、帮派、排行框架;深度功能测试还确认了它的血统——和 `cctx`(驰骋天下)是同一套源码的独立分支(注册流程的天赋选择提示文字逐字相同,`adm/daemons/logind.lpc` 里至今还留着一行读取 `/adm/etc/cctxinfo` 的残留代码,"cctxinfo" 这个档案名正是 `cctx` 的痕迹,并非 `xkx2001`/`xuanjianlu` 所属的 XKX 家族)。客店留言板上还完整保留着真实的历史玩家留言——bug 报告、任务修复公告、平衡性调整通知、门派讨论——是这批档案里少见的、原始玩家社群历史被完整保存下来的例子。

English

A Traditional Chinese wuxia MUD drawing its plots and riddles from Jin Yong's fifteen novels. Players start out at the Dragon Gate Inn and make their way through sects and cities across the jianghu, training internal power, external martial arts, and other skills along the way — a classic wuxia framework of stats, sects, master-disciple bonds, guilds, and rankings. Its inn's bulletin board still carries real historical player messages — bug reports, quest-balance patch notes, sect gossip — a rare case in this collection of an original community's own history surviving intact. Its lineage traces back to cctx (Chi Cheng Tian Xia), an independent branch of the same source codebase, down to a lingering reference to a leftover cctxinfo file.

README

深度功能测试新发现的 bug(详见 NOTES.md)

adm/daemons/logind.lpc 的食物/饮水初始化判断条件混用了两个不同的 物件——!user->query("food") && !user->query("water") && ob->query("age") == 14——前两个读 user(玩家身体),最后一个却 读 ob(登录连线物件,全代码库没有任何地方给它设过 age)。这个条 件因此永远为假,每一个全新玩家食物/饮水槽永远是空的,一进游戏 就会看到"你餓得直冒金星"的挨饿提示。是和 cctx 完全相同的坏代码 (AGENTS.md §8.9),因为确认了血统关系才主动去检查、也确实在这里 找到了同一个 bug。已同样改成 user->query("age")。修复前后各注册 一个全新角色对照验证:修复前食物/饮水槽全空,修复后全满。

在线试玩

https://mudlibs.fluffos.info/niaoren/

管理员账号 / Admin account

警告:这是为本地/浏览器单机游玩预置的公开默认密码,如要对外公网
开服,请务必先修改此账号密码(或从 wizlist 中删除该账号)。

本地运行

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

游戏端口:40100。

NOTES · 移植与修复记录

niaoren — 鳥人世界 / "The Bird-Man World" (xkx codebase, MudOS v22-era)

Archive: 最新鳥人世界.zip. Port: 40100. Status: done (boots clean, full registration flow verified end-to-end with a real Chinese name, admin account verified in both native and WASM modes).

What this is

config.cfg's own name field (raw bytes b3 be a4 48 a5 40 ac c9, BIG5-decoded) is 鳥人世界; the in-game banner also spells it out as "THE BIRD-MAN WORLD" and stamps MudOS: v22c2 / * MudOs V22pre11 *. Root in the archive is nested two levels deep: mudxkx/xkx/ (the extraction's outer mudxkx/ wraps the actual mudlib root xkx/, plus a sibling mudos.exe/mudos.log — a Windows MudOS binary distribution, not part of the mudlib itself). config.cfg: master file : /adm/obj/master, simulated efun file : /adm/obj/simul_efun, mudlib directory pointed at a hardcoded d:\mudxkx\xkx (Windows path, ignored — our config.fluffos uses the correct absolute Linux path). This is a Jin-Yong-themed wuxia mudlib (the intro text explicitly says the plot/setting draws on "金庸的 十五本小說" — Louis Cha's fifteen novels), same broad genre as most of this project's other libs, built on what looks like a customized ES-family/xkx codebase (not literally the same lineage as the ES2/Annihilator family seen in huoying — different master.lpc/daemon shapes, no shared copyright header).

Sizable archive: 7028 raw .c files (the vast majority of the tree; a handful of oddly-extensioned leftover/backup copies also exist — see below), no bundled driver source beyond the Windows mudos.exe binary (harmless, left alone, not part of work/... actually IS copied into work/ by the mechanical raw/→work/ copy since convert_lib.sh copies the whole raw root; it's inert, never loaded by the driver, and not worth special-casing out).

Resumed session note

This lib's libs/niaoren/{config.fluffos,raw/,work/,lpcc_*.log} already existed on disk when this session picked it up (a prior agent, stopped for an unrelated reason — wrong model, not a quality problem — had already done the BIG5 conversion, .c→.lpc rename, boot, and initial registration/admin-seeding pass). This session verified and extended that work rather than redoing it: confirmed the encoding fix was already solid, found and fixed 3 additional small leftover bugs (below), applied the fail-closed loopback-gate correction, and did full native+WASM dual-mode verification from a fresh boot (a previous long-running driver instance from the earlier session was still using a stale in-memory compile of one file — see the "stale-process" gotcha below).

The BIG5 "0x5C trail-byte" encoding gotcha (already fixed on disk; this

session's job was verifying and scoping it, not applying it from scratch)

BIG5 is a variable-width, non-self-synchronizing encoding: a lead byte in 0x81-0xFE is always followed by exactly one trail byte, and BIG5's trail byte range (0x40-0x7E and 0xA1-0xFE) includes 0x5C, the ASCII backslash. 88 distinct BIG5 characters have a trail byte of exactly 0x5C (computed directly: bytes([lead,0x5c]).decode('big5') for every lead in 0x81..0xFE) — e.g. 許(b3 5c)/么(a4 5c)/功(a5 5c).

If ANY processing step in a conversion pipeline treats the raw byte stream byte-wise instead of encoding-aware (the classic case: a shell/sed pass that does a blanket "escape every backslash" substitution on bytes that are still BIG5, before the real decode step) that trail byte gets literally doubled: [lead][0x5c] → [lead][0x5c][0x5c]. A subsequent correct BIG5 decode then reads [lead][0x5c] as the intended character (e.g. 功) and is left with one extra literal ASCII backslash trailing it — the character displays correctly but is immediately followed by a stray \ in the decoded UTF-8 text. This is easy to miss because it does NOT throw a decode error (both bytes of the pair are still individually valid under BIG5) — it just silently corrupts adjacent, unrelated text.

Verification performed this session (this is the "targeted sweep, not just the one instance" the resume note asked for): generated all 88 trail-byte-0x5C BIG5 characters, then grepped the entire work/ tree (decoded as UTF-8) for [one of the 88 chars]\ as a regex class across every file, not just .lpc/.h:

Net conclusion for this archive: the trail-byte bug is real, was correctly and completely fixed on the actually-playable source before this session started, and does not need a further sweep — the remaining 657 instances are all in dead files outside the driver's load path.

Stale-process gotcha hit while verifying the above

Before concluding the fix was complete, this session's first check against a still-running driver instance (left over from the prior session, PID confirmed via /proc/<pid>/cwd before touching it — same precaution as huoying's notes) showed a MANGLED title string at runtime: score displayed 䏟 大鳥人 𨧨 around the admin rank name, where adm/daemons/rankd.lpc's on-disk source (byte-verified) has the correct HIW "【 大鳥人 】" NOR. This looked at first like a live instance of exactly the trail-byte bug — but grepping the source found nothing wrong. Root cause: the long-running driver process (started ~before some earlier edit to rankd.lpc) had that file's OLD compiled bytecode cached in memory and was never sent an explicit update /adm/daemons/rankd, so it kept serving whatever rankd.lpc looked like at the time it first compiled — completely unrelated to the current, correct on-disk content. Killed that stale process and did a fresh boot for all verification in this session; the mangled brackets do not reproduce on a clean boot (score now correctly shows 【 大鳥人 】 and 【 平 民 】 for a regular player). Lesson: don't trust a long-running driver instance found already listening on a lib's port as evidence of current-source behavior — always verify PID/start-time against the files' last-modified time, or just kill and reboot fresh before drawing conclusions from live output.

Other fixes applied this session (mechanical, unrelated to encoding)

1. cmds/skill/array1.lpc — inherit SKILL; textually AFTER int mean; (a global variable declaration) — the standard "Illegal to inherit after defining global variables" shape (AGENTS.md §6.1). Reordered (inherit first, then the global). Isolated to this one file (grepped the rest of the lpcc_fail.log for the same error text — no other occurrences). 2. adm/npc/itemmaker.h (included by adm/npc/ganjiang.lpc and d/cloud/npc/itemmaker.h's own copy) — a genuine original-author typo, confirmed present byte-for-byte in the raw pre-conversion archive too: sprintf("\t\tset(\"unit\\", \"%s\\");\n", ...) has one extra literal backslash before several closing escaped-quotes (\"unit\\" where it should be \"unit\"), which prematurely closes the C string literal one token early and desyncs the parser for the rest of the line/file (produces cascading "Illegal character '\'"/"unexpected token" errors, NOT a BIG5 issue — this is plain ASCII). This is a code-GENERATOR template (an admin "make item" authoring tool that emits .lpc source as text), so the bug never affected regular gameplay, only that one admin utility. Fixed by removing the 23 stray extra backslashes (data.replace('\\\\"', '\\"') across the file). Same shape recurred verbatim in clone/misc/objmaker.lpc (its "make armor" sibling tool, 4 instances) — fixed identically. 3. d/cloud/npc/asd.lpc — an NPC's multi-line "long" description was missing the closing/re-opening quote marks between lines entirely (set("long","...人物"\n\n 其武功... — no quote before 其武功), again confirmed byte-identical in the raw archive (a genuine, always-broken original NPC file, not conversion fallout). Fixed by quoting and \n-joining each line properly. Low-impact (one NPC's flavor text), fixed because it was quick and matches the project's precedent for single-file typo fixes (cf. huoying's gai.lpc).

lpcc sweep

scripts/lpcc_check.sh libs/niaoren/config.fluffos libs/niaoren/work: 6796/7032 pass (96.7%), up from 6765/7032 (96.2%) before this session's 3 fixes above (the delta is larger than 3 because itemmaker.h is #included by more than one file). Remaining 236 failures are a long tail spread across dozens of independent world zones (/d/city 14, /d/shaolin 12, /clone/board 10, /d/foshan 9, /kungfu/skill 8, /d/wudang 8, /d/dali 8, /d/city2 8, /adm/daemons/network 8, ...) — sampled several individually (not just pattern-matched) and all are genuine, independent archive gaps: missing rooms a clone tries to move() into at create() time (/clone/board/lpc_b → nonexistent /d/wizard/lpcroom), MudOS-only socket/network primitives with no FluffOS equivalent (adm/daemons/network/*: dump_socket_status, raw socket_create inetd/mailserv/pingd-style daemons), the well-known tail()/ dump_socket_status() missing-efun gaps (§8e-family, unused admin commands), a handful of Undefined variable 'GAOSHOU'/'THIS_MUD'-style missing shared constants in disused daemons (postal/richman/mail system), and more single-file NPC/room typos of the asd.lpc shape that weren't chased further per the "breadth over depth" policy across 3 archives this session. None of these are on the registration/first-room/ admin path (confirmed by the full interactive tests below reaching the game world cleanly with zero runtime errors of these shapes).

WASM-readiness / fail-closed loopback pass (this session)

Found the previous session's loopback patch already used the fail-open shape the orchestrator's mid-task security correction explicitly asks to retire (any malformed/non-string/unparseable IP was being treated as "trust it, must be loopback"). Retrofitted both sites to the fail-closed shape (loopback ⇒ exactly "127.0.0.1"/"::1"/a "127." prefix/"localhost"; anything else, including something the driver couldn't resolve, is now treated as an ordinary remote value and still subject to the gate):

Recompiled both changed daemons live (update /adm/daemons/band, update /adm/daemons/logind) and retested: loopback reconnect within the 16-tick throttle window still succeeds immediately (no "距上次退出僅... tick" message), matching pre-patch behavior for local play.

Boot + registration-flow verification (fresh boot, this session)

Booted ~/src/fluffos/build-debug/src/driver config.fluffos from libs/niaoren/ (per AGENTS.md §5.2, log directory is CWD-relative). Clean boot, only cosmetic nosave/unused-variable warnings, Accepting telnet connections on 0.0.0.0:40100. / Initializations complete..

Registration flow order for this lib (read from adm/daemons/logind.lpc's actual input_to chain, not guessed from prompt text): logon→get_id→confirm_id(y/n)→get_name (Chinese name FIRST, before password)→new_password→ confirm_password→get_gift(accept random stat roll, y/n)→get_email→ get_gender→enter_world. Note this lib's ordering differs from some others in this project (Chinese name is asked before the password, not after) — worth checking per-lib rather than assuming a fixed order.

Full transcript (scripts/mudclient.py), fresh id qinfengniao (must be pure letters, digits are rejected with "你的英文名字只能用英文字母"):

--send ""              # "press any key to continue" banner
--send "qinfengniao"    # id
--send "y"              # confirm new character
--send "秦风"           # REAL Chinese name (get_name -> check_legal_name)
--send "test12345"      # password
--send "test12345"      # confirm password
--send "y"              # accept the random stat roll
--send "[email protected]" # email
--send "m"              # gender
--send "look" / "score" / "quit"

Result: name "秦风" (2 Chinese characters) accepted without any rejection message, character entered the world directly into 龍門客棧 ("Dragon Gate Inn") with full scene text, NPC (店小二), and board present. Reconnect (same id/password) → 重新連線完畢 → same room restored correctly. score showed the correct title bracket rendering (【 平 民 】), correct stats/bars, all in properly-rendered Traditional Chinese. quit saved and disconnected cleanly (歡迎下次再來!). Immediate re-reconnect (well within the 16-tick throttle window) succeeded without the flood message, confirming the loopback exemption survived the fail-closed tightening above.

Admin account

Already seeded by the prior session (this session verified it end-to-end and did not need to recreate it): id fluffos, password Mud@2026, Chinese name 無敵鐵鳥 ("Undefeated Iron Bird" — a fitting pun on 鳥人世界/"bird-man"). Granted (admin) via adm/etc/wizlist's plain-text per-line format (fluffos (admin), alongside the archive's original admin accounts andy/leos/sunpoet/nick/roye... wait, those last few are (arch); the pre-existing (admin)-level accounts are andy, leos, sunpoet, nick). Save files (must be committed — gitignored by default): work/data/user/f/fluffos.o, work/data/login/f/fluffos.o.

Verified THIS session, from a fresh boot, via the standard flow (id → password, no confirm/registration since it already exists): login shows 目前權限:(admin); ran the actual wizard command update /adm/daemons/rankd → 重新編譯 /adm/daemons/rankd.lpc:成功! (and again for band/logind after the loopback-gate edits above) — confirms real wizard-level access, not just a cosmetic permission string. score/quit also verified clean for this account.

WASM verification (scripts/wasm_client.js against build-wasm/src)

Both a fresh registration and the admin login were run against the WASM build (in-process driver, work/ copied into MEMFS — this does NOT write back to the host's real save files, so no WASM-side test leaves save-file cleanup to do):

Verdict: native OK + WASM OK.

Process/cleanup notes

深度功能测试(第二轮,2026-08-03)

此前的验证只做到浅层冒烟测试,而且血统标记为"未分类"(AGENTS.md 里 明确记载"treat as unclassified until someone actually diffs it against xkx2001/xuanjianlu")。本轮做了两件事:解决了血统悬案,并发现修复 了一个真实 bug——这个 bug 正是因为血统关系才被联想到要去检查的。

血统悬案解决:和 cctx(驰骋天下)是同一套代码库的独立分支

启动前先按 AGENTS.md 的建议,把这份档案的 master.lpc 和 xkx2001/xuanjianlu 做了字节级 diff——确认不是同一血统(两两 之间都有 500+ 行差异,相对于全文件只有 340-349 行,差异比例太高, 不可能是"同一份代码的少量修改")。之前"未分类"的判断本身是对的,只 是比对错了对象。

真正的血统线索出现在实测注册流程中:天赋选择提示"一個人物的天賦對 于他或她所修習的武藝息息相關。俠馳騁江湖中的人物大多具有以下四項天 賦",这段文字(连"俠馳騁江湖"这种不常见的措辞都一字不差,只是繁简 转换)和本次会话刚刚深度测试过的 cctx(驰骋天下)注册流程里的同 一段提示逐字相同。进一步确认:adm/daemons/logind.lpc 里至今还留 着一行 write(read_file("/adm/etc/cctxinfo") + "\n");——cctxinfo 这个档案名本身就是"cctx"的残留痕迹,adm/etc/cctxinfo 这个档案也 确实存在于这份代码库里,从未在分支/改名过程中被重新命名。这是确凿 的证据:niaoren 是从 cctx 同一套源码分支出来、重新包装成"鳥人 世界"(金庸十五本小说背景)品牌的独立版本,不只是"看起来像"。已把 这条血统关系写进 AGENTS.md §11 的血统地图。

由血统关联发现并修复:食物/饮水初始化门槛读错了物件(AGENTS.md §8.9 第二例)

既然确认和 cctx 同源,就顺手检查了 cctx 那次深度测试发现的 §8.9 类 bug(enter_world()-等价函式里食物/饮水初始化门槛混用了 user/ob 两个不同物件)是否也存在——确实存在,一字不差的同 一处坏代码:adm/daemons/logind.lpc 第 522 行 if (!user->query("food") && !user->query("water") && ob->query("age") == 14),前两个条件读 user(玩家身体),最后一 个却读 ob(登录连线物件)。和 cctx 一样,clone/user/login.lpc 全代码库没有任何地方给 ob 这个物件设过 age,而 clone/user/user.lpc 的 update_age()(由 setup() 呼叫,在这个 判断之前就已经执行过)确实会把 user 自己的 age 设成 14。 ob->query("age") 因此永远回传驱动默认值 0,判断条件永远为假, 每一个全新玩家食物/饮水槽永远是空的,第一次 look/score 就 会触发"你餓得直冒金星,實在是頂不住了"的挨饿提示。已把 ob->query("age") 改成 user->query("age"),和 cctx 的修法完全 一致。修复前后各注册一个全新角色对照验证:修复前 score 显示食物/ 饮水槽全空,伴随挨饿提示;修复后两条槽全满,没有挨饿提示。这也是这 个 bug 类别第一次在"因为确认了共享血统而主动去检查手足档案是否也有 同一个 bug"这种方式下被发现的,而不是独立踩雷。

完整验证:从注册到留言板

用全新账号在原生驱动上完整走通:英文 id(3-8 个字母)→ y 确认 → 中 文名字 → 密码 + 确认 → 天赋摇点(y/n 确认)→ 电子邮件 → 性别 → 进 入"龍門客棧"(客栈门口挂着一个牌子(paizi))。店小二 NPC 主动搭话 ("這位小兄弟,進來喝杯茶,歇歇腿吧"),确认 command() 自呼叫对 话链路正常。look board 显示"客店留言板"——一块保留着真实历史玩家 留言的公告板,标题涵盖 bug 报告("少林超大bug!!!!!")、任务 修复公告("單正quest 已修好"、"明教乾坤大挪移quest已增加")、平衡 性调整("東方不敗的武功稍做調整(變強一點點)!")、门派讨论("逍遙派 到中央")等,是这批档案里少见的、完整保留下来的原始玩家社群历史记 录,很有保存价值。quit 干净退出(丢弃不值钱的布衣,"歡迎下次再 來!"),并确认下次连线会从当前位置继续("當你下次連線進來時,會 從這裡開始")。debug.log 除了驱动启动期噪音外没有来自本次实际游玩 会话的运行时错误。

未覆盖范围(诚实说明)

预算集中在验证血统关系、食物/饮水 bug 修复、以及基础注册/移动/留 言板流程,没有走到:拜师(武馆在客店西南方向)、战斗、门派系统的 实际互动。这些留给下一轮,目前的验证边界如上所述。

深度功能测试(第二轮,2026-08-18)——补完拜师、战斗测试,均正常工作

补完上一轮留下的拜师、战斗测试。两者都验证正常工作,未发现新 bug。

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

深度功能测试(2026-08-13,round two,新驱动重测)

上面"第二轮"(2026-08-03)那次早于今天的驱动重建(全库 quest_times/win_times %-operator 修复 + Warning/warning 驱动 文本回退),不能算作针对当前驱动的覆盖——这是真正针对今天驱动的重 测。log_error()(adm/obj/master.lpc,CRLF 行尾档案)已经在更 早一轮正确修复过(strsrch(message, "arning:") 大小写无关判断, 注释虽然还写着"驱动文本是小写"的旧记录,但判断本身与大小写无 关,今天的驱动文本回大写后依然正确工作),管理员账号 (fluffos/Mud@2026,adm/etc/wizlist 已有 fluffos (admin) 且存档已提交)也已经用真实注册流程创建并提交过,本轮只发现并修复 了 log_file() 一处。

发现并修复的 PROGRAMMING bug

1. log_file()(adm/simul_efun/file.lpc,CRLF 行尾档案)完全没 有 assure_file() 保护(AGENTS.md §7.11-class 的又一确认实 例):feature/dbase.lpc/inherit/item/combined.lpc 等文件 多处 log_file("trace/...", ...) 调用依赖对应子目录存在。注册 /登录本身只写 log_file("USAGE", ...)(无子目录,本来就存 在),不受影响。已补上 assure_file(LOG_DIR + file);(含前向 声明,用 Python 字节级替换保留原 CRLF 行尾格式)。

Proactive checks(无需改动)

实测过程

登录前有一个"輸入任意鍵繼續..."(press any key to continue)分页 步骤,紧跟在欢迎横幅之后、id 提示之前,和本轮 ldtxii 遇到的形状 完全一样——已提前读代码确认,避免重蹈覆辙丢失第一个真实输入。用已 提交的 fluffos/Mud@2026 登录,score(画面繁体输出"目前權 限:(admin)")确认权限正确,update /adm/simul_efun/file(就是本 轮改过的文件)确认可正常重新编译。adm/log/debug.log 时间戳全程 未变化(Jul 24,早于本次会话),确认无新增未捕获运行期错误。登 录本身产生的存档时间戳类微小 diff 已用 git checkout 撤销,不提 交。驱动最终按精确 PID kill,ps -p 确认已退出。

深度功能测试(第四轮,2026-08-19)——经济系统(商店买卖)验证,发现并修复两个真实 crash

前几轮已确认战斗、拜师均正常工作,唯独经济系统(真实商店买卖)一 直未测试。本轮专门针对欢迎文本提到的"到雜貨店去買金絲甲"、"到醉 仙樓去買東西吃"这两家店做完整验证,过程中发现并修复了两个独立的 真实 programming bug(均为无保护的运行期错误,非平衡性/内容设 计问题),验证了经济系统本身是可用的。

Bug 1:toptend.lpc::topten_add() 的 sscanf 传错参数(AGENTS.md

排行榜家族 bug 的第 N 个独立实例,同一 codebase 血统里的确认)

新号注册流程(logind.lpc::get_gender() → enter_world() → TOPTEN_D->topten_checkplayer() → topten_add())在 debug.log 里稳定触发一条未捕获运行期错误:

執行時段錯誤:*Bad argument 1 to sscanf
Expected: string Got: ({ ... }).
程式:/adm/daemons/toptend.lpc 第 319 行

topten_add() 解析既有排行榜文件的 fallback 分支把整个 explode() 结果数组 astr 传给了 sscanf()(应该传当前行 astr[i]):

if(sscanf(astr[i],"%s(%s)%d",name,id,data)!=3)
    if(sscanf(astr,"%s(%s)%d;%*s",name,id,data)!=3)   // 应为 astr[i]
        return notify_fail(...);

这正是 AGENTS.md 里已经记录过的、toptend.lpc 这个共享守护进程反 复出现的第三种独立 bug 形状("whole line-array passed to sscanf instead of current line",此前在 xixingzhanji 等库独立发现过), niaoren 与 cctx 共享同一份 toptend.lpc(含 add by jackyboy@cctx 1999/3/8 注释残留),这次是这份共享代码在 niaoren 这一支里独立复现。因为 enter_world() 里 user->move(startroom) 先于 TOPTEN_D->topten_checkplayer() 执行,未捕获错误只会截断 enter_world() 尾部两个 set_temp("temp_exp"/"temp_time", ...) 调用,不影响玩家进入起始房间本身——注册/进游戏表面上看起来完全正 常,只有翻 debug.log 才能发现。修复:astr → astr[i](第 319 行),update /adm/daemons/toptend 热编译后用第二个全新角色重新 注册验证:debug.log 干净,无新增 執行時段錯誤。

Bug 2:feature/dealer.lpc(商人 mixin)对 vendor_goods 里指

向不存在档案的条目没有任何保护,导致 list/buy 直接崩溃

d/city/npc/yang.lpc(雜貨鋪老板 楊永福,正是欢迎文本"到雜貨店去 買金絲甲"指的那家店)的 vendor_goods 数组里有一条 "/u/sunpoet/torch.lpc"——这份档案在整个 archive 里根本不存在 (work/u/sunpoet/ 目录都没有;sunpoet 是原始 admin 账号之一, 这条大概率是残留的、从未真正落地的赠品引用)。feature/dealer.lpc 的 is_vendor_good()/do_list() 对 vendor_goods 数组里每一项 都直接 ->id(arg)/->short()/->query("value"),任何一项文件 缺失都会抛出未捕获的 *call_other() couldn't find object 错误, 且是无保护的——玩家在楊老板店里打 list 或 buy <任何东西> 都会先崩这一条,list 崩到看不全后半段商品,buy 直接崩到完全买 不了任何东西(is_vendor_good() 遍历数组时崩在缺失条目上,永远走 不到匹配的正常条目那一步,取决于崩溃条目在数组里的位置)。这是一 个真实 crash(有明确 debug.log trace,非"店家拒绝"/"功能未实现" 这类设计范畴),修复方式沿用项目里 §7.115(QUEST 宏)同款"保护 调用点,不发明缺失档案"的原则:is_vendor_good()/do_list() 里 每次访问 vendor_goods[i] 前都包一层 catch(),缺失条目静默跳 过、不影响数组里其余条目正常展示/购买。这个 mixin 是通用商人基 类,sell/value 两个函数没有类似逐项遍历 vendor_goods 的写 法,未改动。修复后重新验证:list 完整列出楊老板全部商品(缺失 的那一条被跳过,后面的"袋子"等条目正常显示),buy jia(金絲甲 的实际关键字是 "jinsi jia"/"jia",不是欢迎文本字面的 "金絲甲"三个字,第一次用错关键字试成了"你想買什么?",换关键字后 成功)扣款、拿到物品全部正确。

经济系统完整验证(两笔真实交易,金额扣减/物品到手均正确)

用已注册好的测试号 qinjinghuo(管理员账号 eval MONEY_D->pay_player(...) 现场充值 10 兩黃金,模拟"缺錢花,也可以 請老玩家幫你暫渡難關"这句欢迎文本里描述的、新手找人接济的机制, 而不是发明一个游戏本身没有的起始资金):

标准 bug 清单快速核对(本轮,均已 corpus-sweep 过,抽查确认无异常)

其他观察(未处理,记录以备将来参考)

清理与收尾

测试号 qinjinghuo(经济测试主号)、qinerhuo(topten 修复验证 用的干净对照号)的存档在验证完成后已删除 (work/data/{user,login}/q/{qinjinghuo,qinerhuo}.o)。管理员账 号 fluffos 因本轮登录/update/eval 操作产生的存档时间戳更新 属于正常存档演进,已随本轮改动一并提交。驱动全程用精确 PID kill(两次重启各自记录 PID,kill <pid> + ps -p <pid> 确认已退 出),未使用任何 pattern-match 方式。

AGENTS.md §7.100 fix (2026-08-19): redundant replace_program(ROOM) landmine

Same corpus-wide bug as the batch-1-6 sweep (ROOM macro "/inherit/room/room" from include/globals.h). fix_710_room.py deleted 2,339 live standalone replace_program(ROOM); lines; a follow-up hand fix caught 6 more files with the irregular "space-before-semicolon" shape (replace_program(ROOM) ;) the strict script correctly left alone — d/shenlong/dahai.lpc, d/quanzhou/dahai.lpc, d/taohua/chufang.lpc, d/taohua/dahai.lpc, d/xiakedao/duchuan.lpc, d/xiakedao/dahai.lpc — plus the room-building tool's string-builder template (work/clone/misc/roommaker.lpc). Total 2,346 live occurrences removed, matching the survey's count exactly. No .lpc files under work/data/ at all in this lib, so no false-negative risk there.

Verified: clean build-debug boot (zero new compile errors, zero "cannot replace"/"cannot bind" in debug.log), live admin login (fluffos/Mud@2026, traditional-Chinese client) into 巫師休息室, look/score/quit all worked cleanly. Incidental data/{login,user}/f/fluffos.o save drift from the login test was reverted via git checkout HEAD before committing.

§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): 4 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.

深度功能测试(§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-05)— 房间列出的物件缺档时房间照常加载

房间基类 inherit/room/room.lpc:make_inventory() 用 catch() 包住 new(file),做不出来就返回 0;reset() 的 case 1: 拿到 0 就跳过这一项(if (!objectp(ob[list[i]])) break;);reset() 的 default: 拿到 0 就跳过这一项(if (!objectp(ob[list[i]][j])) continue;)。档案里没有的 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。

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

scripts/lpc_warnings.py niaoren --fix 加 scripts/lpc_fix_no_effect.py --apply,共改动 500 个 .lpc 和 14 个 .h(多为删去未用的局部变量)。HEAD 与工作树分别加载(新进程):471 -> 471 通过,无回退;改过的头文件的 146 个包含者 146 -> 146 通过。启动时警告 16 -> 0;新进程登录(输入名字)正常。

深度功能测试(§10.7,2026-10-11):“无副作用表达式”警告复查(批 5)

深度功能测试(§10.7,2026-10-12):编译警告收尾

可编译文件中的编译警告 92 → 0,不加 pragma,逐条在源头修。

改动的 27 个文件与继承两个房间基类的对象共 29 个,HEAD 与工作树各载入一次:HEAD PASS 24,tree PASS 24,regress 0;5 个 FAIL 两边相同(adm/npc/luban、d/quanzhou{,.old}/npc/tang{,3} 批量载入时 restore_object 读存档被拒,运行期问题)。lpc_audit_removed_locals.py 0 处待看。

无门派玩家的 family 映射(2026-10-15,KB 05 §7.30 第三种)

mapping fam = ob->query("family"); 之后直接 fam["family_name"]:没有门派(新人或叛师后)的玩家在学习、练功、拜师、进入门派房间时报 Value being indexed is zero,而不是作者写好的提示。11 处在声明行加上 if (!mapp(fam)) fam = ([ ]);(scripts/lpc_fix_family_guard.py)。

东方故事/西游记体系的技能文件(2026-10-15,KB 06 §7.241 第三类)

这些 kungfu/skill 文件来自 gin/kee/sen/force 体系,本库人物只有 jing/qi/neili:receive_damage("kee", 30) 报 伤害种类错误,query("force") 永远为 0,技能学不了也练不了。按 kee→qi、gin/sen→jing、force→neili、max_force→max_neili、force_factor→jiali 改了属性名(scripts/lpc_port_es_props.py),共 1 个文件。

其他体系的属性名(2026-10-15,KB 06 §7.241 第三类,--any-file)

另有 3 个文件(绝招、状态、物品、NPC 等,不限于 kungfu/skill)来自 gin/kee/sen/force 体系,同样按 kee→qi、gin/sen→jing、force→neili 改名(scripts/lpc_port_es_props.py --any-file)。

family 映射(再补)(2026-10-15,KB 05 §7.30)

mapping fam; fam = (mapping)ob->query("family");(先声明后赋值)和泥潭式的 query("family", me) 同样直接取下标:无门派的新人拜师(attempt_apprentice/recognize_apprentice)、NPC 的 greeting、房间的 valid_leave、ask_me 等报 Value being indexed is zero。3 处补上 if (!mapp(fam)) fam = ([ ]);(刚 recruit_apprentice() 之后的除外)。

空手使用兵器绝招(2026-10-15,KB 06 §7.246)

perform 指令允许直接点名招式(如 perform stick.ban),这些绝招读了 me->query_temp("weapon") 却从不检查,空手时 weapon->name() 报错(有的已先扣了内力)。在赋值后补 if (!objectp(weapon)) return notify_fail("你必须手持兵器才能使用这一招。\n");(scripts/lpc_fix_perform_weapon.py):duanyun-fu/du1、wuyue-blade/wu1。

把经验、属性、内力当技能读(2026-10-15,KB 06 §7.247)

经验、属性、内力上限不是技能,xkx/ES 的 query_skill() 对它们只返回 0(风云的还会去载入同名技能文件而报错)。改为 query("...")(scripts/lpc_fix_pool_as_skill.py):dragon-whip(内力修为门槛 query_skill("max_neili") < 500 永远不过);liancheng-jian/li1、liancheng-jian/li2(random(me->query_skill("combat_exp")) > target->query_skill("combat_exp") 恒为 0 > 0,这一招永远不成功)。

技能名拼错(2026-10-15,KB 06 §7.249)

query_skill() 里的技能名拼错了,或用的是本库改名前的旧名;本库没有这个技能,调用永远得 0:门槛(< N 就拒绝)永远过不去,相关功夫或绝招学不了、用不了;内功互斥(> N 就拒绝)从不生效;公式里这一项一直是 0。经汉字名(提示文字或中文名词典)或纯拼写差异(_/-、少一个连字符、相邻字母颠倒)核对后,改为本库的技能名(scripts/lpc_fix_skill_typo.py):beimin-shengong→beiming-shengong(jiuyang-shengong、taiji-shengong、xiantian-gong);taijishengong→taiji-shengong(beiming-shengong/god-sword/godangry、god-sword/go1);mizongxinfa→mizong-xinfa(longxiang/f);tanzhi_shentong→tanzhi-shentong(tanzhi-shentong/ta1)。

八识神通 drift_sense 少一个括号(2026-10-15)

bashi-shentong/drift_sense 第 55 行改条件时漏了右括号(if( random(me->query_skill("magic")) < 100 {,原条件还注释在上一行),文件编译失败。补上括号,保留本库改过的 < 100。本库目前没有地方传授八识神通。

family 映射内联取下标(2026-10-15,KB 05 §7.30)

ob->query("family")["master_name"]、me->query("family")["family_name"](泥潭式为 query("family", ob)[...])直接对门派映射取下标:没有门派的人(新玩家、叛师者、大多数 NPC)在 NPC 的打招呼和问话、绝招的门派判断、房间的 valid_leave 里报 Value being indexed is zero,而不是得到拒绝。8 处(6 个文件)改为 dbase 路径查询 query("family/master_name"):有门派时值相同,没有时得 0;原有的判断照常生效(scripts/lpc_fix_family_index.py)。给映射赋值的写法未动。

移进不存在的巫师房间(2026-10-15,KB 06 §7.251)

用完的物品、要“离开”的 NPC、掷在空中的兵器被移进巫师的工作室(/u/wind/),本库没有这些房间。本库的 move() 用 call_other(dest, "???") 载入目的地,找不到文件就报 call_other() couldn't find object,后面的语句都不执行:被雇来打晕或杀人的李凌(d/city2/npc/liling2)办完事要离开时报错,留在原地。1 个文件改为移进本库的虚空房间 VOID_OB(/clone/misc/void),与原作把它们“停放”在巫师房间的用意相同。

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 载入不了,刷出它的房间也少一个人。本库有 4 个对象设了本库没有文件的武功(5 个名字,最多的是 shenlong-quanfa、xiake-force、xiake-strike)。三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。抽样载入这些对象:4 个里 3 个通过,其余 1 个停在别的错误上(kungfu/class/xiake/master:*No program in object '/kungfu/class/xiake/master'!)。(scripts/lpc_guard_set_skill.py)

武功名拼错的 NPC(2026-10-19,KB 06 §7.258)

set_skill("kunlun-shnfa", …)、map_skill("dodge", "shaolin-shnfa") 这样的调用把武功名拼错了:本库没有这个武功文件,有的是拼对的那个。以前这种 NPC 在 create() 里报 No such skill 载入不了;武功缺失时跳过之后它们能载入,可是少了那门武功(set_skill 被跳过,map_skill 映射不到,门派弟子用普通轻功/拳脚)。按核对过的对照表改正,共 10 处、9 个文件:xinxiu-duzhang→xingxiu-duzhang(2 处)、tiaji-jian→taiji-jian(2 处)、lingbo-weibu→lingboweibu(2 处)、yan-fly→yanfly(2 处)、juemen-gung→juemen-gun(1 处)、taiji-shenfong→taiji-shengong(1 处)。(scripts/lpc_fix_skill_names.py)

调用了父类没有的函数的对象(2026-10-19,KB 06 §7.259)

::create();、::init();、return ::buy_object(who, item); 这类调用写的是父类里有这个函数的版本;本库的父类没有,旧驱动把它当空操作(返回 0),现在的驱动在编译时就报 Unable to find the inherited function,整个对象载入不了。单独成句的调用删掉,return ::f(...) 改成 return 0;(void 函数里是 return;),与旧驱动的行为一致。共 1 处、1 个文件:::setup()(1 处)。只改了出错原因全是这一类的文件;还带着别的错误(多半是父类本身没了)的文件没动,等父类补回后再看。(scripts/lpc_fix_dangling_super.py)

laozhe 的 command("give yancao to", me)(2026-10-19,KB 06 §7.266)

d/heimuya/npc/laozhe.lpc(黑木崖的老者)的 init() 里写着 command("give yancao to", me);,command() 只收一个字符串,驱动报 Too many arguments to 'command',整个 NPC 载入不了。照 cctx、fys 等库已经改好的写法,改成 command("give yancao to " + ob->query("id"));(ob 是刚进房间的玩家)。

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,KB 03 §4.6)

转换时丢掉的分号、逗号、引号或文件末尾的 },共 1 个文件,修前 HEAD 载入失败。文件末尾少了 }(最后一个或两个字节丢了),补上:/d/city2/npc/zhengmen.lpc(2 个)。修后与 HEAD 逐个对比载入:这 1 个物件全部载入,没有退步(scripts/lpc_restore_from_twin.py、lpc_fix_eaten_quote.py --comma、lpc_fix_eof_braces.py)。

旧驱动的类型宽松:缺省参数、无类型形参、数组初始化、class 变量(2026-10-19,KB 04 §6.19、§6.20)

原来的 MudOS 把调用少传的参数补 0、把没写类型的形参当 mixed、不检查变量的声明类型与初始值,也允许变量叫 class;这个驱动在编译时全部拒绝,整个物件载入失败。修了 1 个文件:/d/heimuya/ren.lpc(scripts/lpc_round_files.py 的修复轮:调用处补写 0、无类型形参补 mixed、string X = ({...}) 补星号、void 函数的返回值,以及 class 变量改名 pick_class)。逐个文件在 HEAD 和改后载入对比:这些文件里 1 个原来载入失败、现在载入,没有退步。

== !"字符串" 多出来的 !(2026-10-19,KB 06 §7.280)

!"taoist" 是整数 0,x == !"taoist" 就是“x 等于 0”,作者为其余所有人写的那一支永远走不到。具体是:龙女对古墓弟子的机关暗门提示永远给不出来(5 处);孙婆婆的“你去吧”只对 class 没设置的人生效;性别检查(“你不是女的,无法领会其中至阴之理”)永远不拦人,男的照样能学玉女心法。全部改成 != "字符串"(scripts/lpc_fix_not_literal_cmp.py):/kungfu/class/gumu/long.lpc(5 处)、/kungfu/class/gumu/sunpopo.lpc(1 处)、/kungfu/skill/yunu-xinfa.lpc(1 处)。同样的行在 xkx 一系的 35 个库里都有。

x != "a" || x != "b" 和 x == "a" && x == "b":同一个值的比较链永远成立/永远不成立(2026-10-20,KB 06 §7.282)

if (arg != "wan" || arg != "丸") return notify_fail(...) 对任何 arg 都为真,dir == "south" && dir == "north" 永远为假:条件下面的守卫要么永远拦、要么永远不拦。作者要的是“既不是 A 也不是 B”(&&)或“是 A 或 B”(||)。!= 链的 || 改成 &&(仅当条件下面是 return 的拒绝/守卫),== 链的 && 改成 ||(scripts/lpc_fix_cmp_chain.py)。改动:/d/mingjiao/shanmen.lpc(1 处)。

if (x = 常量):条件里写成赋值的比较(2026-10-20,KB 06 §7.290)

if (arg = "button" || arg = "按钮")、if (!arg || arg = "") return 0;、else if (check = 0 && tempB != ""):把 == 写成了 =,条件的值就是那个常量,分支对所有非零数和任何字符串(连 "" 都算)永远成立、对 0 永远不成立,变量还被覆盖;驱动对此没有任何警告。每一处 = 改成 ==(scripts/lpc_fix_cond_assign.py,共 5 处)。改动:/d/city4/puke-room.lpc(4 处)、/d/hangzhou/npc/meipo.lpc(1 处)。

socket / dumpsocket 指令:dump_socket_status() 驱动里没有(2026-10-20,KB 06 §7.291)

这些巫师指令 write(dump_socket_status());,MudOS 的 efun 在 FluffOS 里不存在,指令编译不过。在模拟 efun 文件里补了 dump_socket_status():用 socket_status() 排出同样的表(没有 sockets 包的构建,比如 WASM,只有表头;scripts/lpc_add_sefun.py dump_socket_status)。

tail 指令:tail(file) 驱动里没有(2026-10-20,KB 06 §7.291)

cmds/*/tail.lpc 调用 MudOS 的 tail() efun,FluffOS 没有,指令编译不过。模拟 efun 文件里补了 tail(file)(Discworld 家族的兼容版:文件最后约 20 行,从完整的一行开始,存在返回 1;scripts/lpc_add_sefun.py tail)。

登录密码超过 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)。

valid_read() 不信任 sefun 对象的 euid "NONAME"(2026-10-20,KB 06 §7.295)

启动时 Too deep recursion. 两次(/adm/simul_efun/file.lpc 的 log_file() → assure_file() → file_size() → 主控 valid_read() → SECURITY_D->valid_read() 拒绝 sefun 对象(它的 euid 是 "NONAME")→ 拒绝记录又调 log_file()),之后每次 log_file() 都递归,日志文件一个也写不出来。valid_write() 早就信任 "NONAME",valid_read() 补上同样的判断(securityd.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。

华山论剑排行榜:第一场比武就读不到 /data/ladder.txt(2026-10-20,KB 06 §7.326)

比武裁判在一场比武结束时先 list = explode(read_file("/data/ladder.txt"), "\n"),读完再 rm 并 write_file 写回;/data/ladder.txt 是比武本身写出来的,全新的树里没有,第一场比武结束就是 Bad argument 1 to explode(),排行榜永远建不起来。把写回之前的那一次读改成 read_file(...) || ""(空表走原来的 lost == -1 && win == -1 分支,把第一对名字写进去);写回之后的第二次读文件已经存在,没有动。加载检查各 1/1。

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])) ...,不检查列表是否为空。本库现有的 NPC 文件里没有字面写成空 chat_msg 的(扫描只认 set("chat_msg", ({ })));守卫挡的是运行时才为空的列表和以后新增的 NPC。在 random(100) < chance 前面加 sizeof(msg) &&(scripts/lpc_fix_chat_empty.py;if (random(100) > chance) return 0; 的写法是 !sizeof(msg) ||)。改动:/inherit/char/npc.lpc(共 1 个文件)。

blockade / recover:data/keep 不存在,解封之后世界仍然锁着(2026-10-21,KB 06 §7.340)

管理员扫描里 blockade 报 Error copying file from /data/user/f/fluffos.o to /data/keep/fluffos, Error: No such file or directory,随后的 recover 在第一个 cp() 上死于 */data/keep/fluffos: lstat failed,来不及执行最后的 LOGIN_D->set_madlock(0):封锁解不开。data/keep/ 是运行时目录,git 克隆里没有空目录。blockade 在第一次写之前 if (file_size("/data/keep") != -2) mkdir("/data/keep");(mkdir() 失败答 0,不抛错),recover 对没有保存档的玩家 if (file_size(src) < 0) continue;,其余照常恢复、照常解锁(scripts/lpc_fix_keep_dir.py,hy5 上实测:没有保存档时 recover 答“时空解封完毕”,blockade 建出 data/keep 并保存,recover 取回并删除)。改动(1 个文件):/cmds/adm/blockade.lpc。

注册到不存在函数的 add_action(2026-10-21,KB 06 §7.349)

add_action("函数", "动词") 绑定的函数,在对象本身、它包含的头文件、它继承的父类和继承它的子类里都没有定义(scripts/lpc_scan_callout_targets.py 静态解析,子类用传递闭包复核,scripts/lpc_verify_handlers.py 运行时 function_exists() 复核)。驱动从玩家最新的一条动作往回找,遇到这样的句子就报 *Function for verb 'drop' not found.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 4 条注册(3 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/d/city4/qishi.lpc(do_pass、do_resign)、/d/wudang/tyroad13.lpc(look_feng)、/inherit/misc/fboard.lpc(do_list)。最多的函数名:do_pass 1 处、do_resign 1 处、look_feng 1 处、do_list 1 处。

对象路径里的 ..:new(__DIR__ "../obj/x") 报 Read access denied(2026-10-22,KB 06 §7.351)

__DIR__ 是当前文件所在目录(带结尾斜杠),__DIR__ "../obj/x" 就是 /d/zhiye/npc/../obj/x;驱动的 legal_path() 不接受带 .. 的路径,new() / load_object() 直接抛 *Read access denied.(实测,find_object() 对它答 0)。走库自己的 carry_object() / 房间 objects 表的路径,库的基类会先规范化,能用;直接把路径交给 new() / clone_object() / load_object() / move() 的不行,调用到那一步才报错。改成规范化后的绝对路径(scripts/lpc_fix_dotdot_dir.py --direct):/d/wudang/npc/xiaocui.lpc(__DIR__ "../obj/nuerxiang" → "/d/wudang/obj/nuerxiang")。

缺失物品的 new() / carry_object() 调用链加了保护(2026-10-22,KB 06 §7.73 补充)

new(路径)->move(...); / carry_object(宏("x"))->wear(); 这类语句,路径指向的文件在这个压缩包里没有:new() 返回 0,->move() 在 0 上报 Bad argument 1 to EFUN call_other()。在 create() 里这个对象永远加载不了,在 reset() 里第一个缺失物品之后的刷新每次 reset 都被跳过。1 个文件里 1 处(new)改成 { object new_ob = new(路径); if (new_ob) new_ob->move(...); }(scripts/lpc_guard_carry_object.py --new:路径按这个文件自己看到的宏解析,宏名冲突、虚拟对象路径一律跳过)。 对这 1 个改动文件做加载检查(HEAD 与工作区对比):HEAD 能加载 1 个,现在 1 个(新增可加载 0 个,回归 0 个)。

调用了、但这个库自己的守护进程里没有的函数(2026-10-22,KB 06 §7.354)

MACRO->函数() 调用的函数在这个库的守护进程里不存在:call_other() 对不存在的函数不报错、只返回 0,所以这些功能(时间显示、昼夜事件、名字检查、价格文字……)一直没起作用。同一血统另一个库的同名守护进程里有这些函数,原文照抄到本库守护进程末尾(scripts/lpc_port_function.py;调用处传的参数个数、函数体用到的函数 / 全局变量 / 宏 / 字符串键都在本库里有,已核对;守护进程在 HEAD 和工作区都能加载,每个函数用固定参数各调用一次:HEAD 返回 0,现在返回正常的值):/adm/daemons/chinesed.lpc ← aoxiangtianji:chinese_month()(2 处调用);/adm/daemons/chinesed.lpc ← bxsj:chinese_time()(1 处调用)。

(: 函数 :) 指针指向对象自己没有定义的函数(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 个函数没有补(item_desc 1):原型还在函数体丢了、或者根本没写过,没有可以直接补回的捐赠(别的库同一路径的文件里没有这个函数,或者它还要用本库没有的辅助函数、签名不同),不编造:/d/city/ximenroad.lpc:look_gaoshi。

运行期目录 log/nosave 不存在,写日志的 write_file() 直接报错(2026-10-22,KB 03 §4.3 补充)

这个库的 5 个文件里有 5 处 write_file("/log/nosave/...") / log_file("nosave/...")(static 改 nosave 时连字符串里的日志路径一起改了,原来是 log/static/):/log/nosave/FENG、/log/nosave/KILL_PLAYER、/log/nosave/ATTEMP_KILL 等。压缩包和站点包里没有这个目录,write_file() 在目录不存在时抛 *Wrong permissions for opening file /log/nosave/... for append. "No such file or directory",调用它的函数(杀人记录、重启记录、call 日志……)当场中断。把 log/nosave 记进 scripts/wasm_keep_dirs.txt(站点和 pristine 树都按它建空目录);没有把字符串改回 static。证明(scripts/lpcc_eval.py,catch(write_file("/log/nosave/ZZTEST", ...)),fqyy2 / hy5 / bmxkx2001 的新进程):补目录前报上面的错误,补后写成功。

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

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

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

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

本库 45 处(31 个文件):拼名字的赋值 23 处、与汉字常量比较 22 处。例:name[0..1] → name[0..0](/d/tianlongsi/npc/kurong.lpc:402);name[2..3] → name[1..1](/kungfu/class/emei/miejue.lpc:125);s1[0..1] == "。" → s1[0..0] == "。"(/adm/npc/luban.lpc:2184)。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.363)

老档按字节做「一个汉字」:while (i--) if (i % 2 == 0 && !is_chinese(name[i..<0])) 只检查偶数下标,奇数位上的拉丁字母(张a)会过;茶馆猜诗 mixup() 用 strlen(str)/2 再 j=j+j、str[k..k+1] 交换两字节;泥潭系 channeld 的 switch (strlen(fname)) case 4/6/8/10 和 strlen(family)==4/6 按字节长度给同盟 / 门派名加空格。本驱动的 strlen() 和区间都按字符算。scripts/lpc_fix_byte_char.py:去掉奇偶项、把区间改成 name[i..i],按字符重写 mixup() 并把 strlen(first)>=14 折成 7,频道标题的 case / 门派长度改成字符数(对照 aoxiangtianji)。本库 汉字循环 / is_chinese 区间 3 个文件;茶馆猜诗 mixup 1 个文件。证明(scripts/lpcc_eval.py):sjplii 的 check_legal_name 在 HEAD 接受「张a」、改后拒绝,两边都接受「张三」;xkx100 的 clubpoem->mixup("离离原上草") 得到「离离原草上」(真交换汉字);nitan_san 两字同盟名得到 【 兄 弟 】。关键对象加载通过。

血条 / 表情标题按字节算显示宽度(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(一格两个空格)。本库 8 处(4 个文件)。证明(lpcc_eval.py hhsj):tribar_graph(6,9,12) 在 HEAD 画出整条 12 个■,改后为 6 个■+3 个□;麻将 strlen(tempB)/2 是两字符牌码,未改。

死巫师出口 gold/dock、sunrise、mei(2026-10-23,KB 06 §7.370)

/d/wanjiegu/road4 东向 /u/g/gold/dock、峨眉寿星居南向 /u/xinyue/sunrise_room、玩家卧室通往 /u/mei/… 均无对应房间。scripts/lpc_fix_drop_dead_u_exits.py 删除这些出口行,并去掉同方向的 create_door(否则 create_door 无出口会炸)。本库 1 处。证明:zxty08nxgbb 的 road4 HEAD 有东向,改后没有;mei_room1 可加载。

tianluo-diwang 知识武功引号剥离(2026-10-27,KB 04 §6.25 Also)

kungfu/skill/tianluo-diwang.lpc(Kayin 古墓知识武功短桩)丢了 inherit SKILL 后的 ; 以及几乎所有引号(return knowledge、query(dex)、汉字通知里字间插空格)。按原意重写成可加载的 knowledge skill(未并入带招式的战斗版手足)。证明:HEAD 加载失败;改后加载成功。

深度功能测试(2026-10-27,§6.25 zhongling tell_object 引号错位)

d/wanjiegu/npc/zhongling(及同路径副本)的 tell_object("who, 钟灵脱下一双绣花鞋。\n") 把对象参数写进了字符串;改为 tell_object(who, "…")。证明:原文错位;改后加载,语料残渣 0。

深度功能测试(2026-10-27,§6.25 one-arg message_vision)

message_vision("…") 补第二参(me / TP / this_player())。

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

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