Record of the Mystic Sword

✅ 可玩

玄剑录

xuanjianlu

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

▶ 开始游玩 · Play Now

玄剑录属于 ES II / 侠客行(XKX)引擎家族,核心 master/simul_efun 文件与同门档案 `xkx2001`、`bmxkx2001` 逐字节相同,但拥有自己独立的武林世界与剧情内容,舞台设定在明教、昆仑、侠客岛等门派与地点之间;克隆对象名单直接取材金庸《射雕英雄传》,东邪、西毒、南帝、北丐、中神通"五绝"和全真教丘处机等人物原名原样登场,新人在侠客岛会先被赏善使、罚恶使等门派使者迎接后再习武结交门派;天赋点可自行分配或交给系统随机,巫师登录在普通密码之外还要求一个独立的验证码;文件头带"Cracked by Kafei"署名,和 `xkx2000zxb` 是同一批流通版本(`xkx2001` 本身则属"Cracked by Roath"的另一批)。

English

Part of the ES II / Xiake Xing ("Wandering Blade") engine family (byte-identical core master/simul_efun files to sibling archives xkx2001 and bmxkx2001), but with its own independent wuxia world centered on the Ming Cult, Kunlun, and Xiake Island. Its clone-object roster draws directly on Jin Yong's "Legend of the Condor Heroes": five special NPCs are literally named and modeled on the novel's Five Greats -- 东邪 (Eastern Heretic), 西毒 (Western Venom), 南帝 (Southern Emperor), 北丐 (Northern Beggar), and 中神通 (Central Divine Coordinator) -- alongside the historical Taoist master 丘处机 (Qiu Chuji). New characters are met on Xiake Island by the sect's judge-envoy NPCs before setting out to train and build sect ties; talent-point allocation can be chosen by hand or left to random assignment, and wizard logins require a separate passcode on top of the normal password. Its files carry a "Cracked by Kafei" header, placing it in the same release batch as xkx2000zxb (xkx2001 itself circulated under a different "Cracked by Roath" release).

README

内容亮点

在线试玩

https://mudlibs.fluffos.info/xuanjianlu/

管理员账号 / Admin account

警告:这是一个公开的默认密码,仅供本地/浏览器试玩。正式对外开服前
请务必修改此密码。

本地运行

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

游戏端口:40064。

NOTES · 移植与修复记录

玄剑录 (xuanjianlu) — archive #70

What this is

archives/玄剑录.rar, root at raw/xjl/. config.xjl's name : field and the live connect banner both confirm the game's own self-identified name is 玄剑录 ("Record of the Mysterious Sword") — matches the slug, no divergence to note. Distributed by the same "小熊泥苑" collector/hosting site seen on shujian2008/sjtx2 (archive #35/#36) — 小熊泥苑.txt sits alongside the mudlib root in the raw archive, just site branding, not part of the mudlib.

Lineage: confirmed via md5sum that adm/simul_efun/chinese.c and adm/single/master.c are byte-identical to xkx2001 (archive #25) and bmxkx2001 (archive #45) — this is the same "ES II" engine family (master.c header credits "original from Lil", "rewritten by Annihilator", "modified by Xiang/Xuy for XKX"). adm/register/xkxreg, xkxrestore, xkx_monitor filenames further confirm the "XKX" (侠客行) codebase ancestry, even though this game's own live content/world is a distinct wuxia setting (Ming-cult/明教/昆仑/侠客岛 zones, its own NPCs). adm/daemons/logind.c differs from both siblings (its own registration flow/banner), consistent with "shared engine, distinct game-world content" seen across this whole ES II family (es1_win/esI/xkx2001/rzrmud/xo/ bmxkx2001/kxkj/yueyingqiyuan/wuhanzhan/ yanhuangwuhun/haiyang2).

Layout: adm/single/{master,simul_efun} + adm/daemons/* + adm/simul_efun/* + adm/register/* (the last one XKX-specific, holds a MySQL-free registration helper set, not used by the live registration flow tested here).

Size: 11,492 raw files, 10,605 .lpc/.c files after conversion — a normal-sized lib for this project, not a mega-lib; the full lpcc_check.sh sweep was run without incident (see below).

Fixes applied (and why)

Standard/catalog fixes, applied proactively

1. AGENTS.md §15h (is_chinese/check_legal_name GBK byte-range bug — nearly universal, confirmed present here too): - adm/simul_efun/chinese.lpc's is_chinese(str): was strlen(str)>=2 && str[0] > 160 && str[0] < 255 (a GBK lead-byte range check, always false against UTF-8 codepoints) → replaced with strlen(str)>=1 && str[0] >= 0x4e00 && str[0] <= 0x9fff (CJK Unified Ideographs codepoint range). - adm/daemons/logind.lpc's check_legal_name(name, ob): bound (strlen(name) < 2) || (strlen(name) > 8) || i % 2 (byte-count bound + a meaningless parity check) → (strlen(name) < 1) || (strlen(name) > 4) (character count; the message text already promised "1 到 4 个中文字" — 1 to 4 Chinese characters — so halving 2/8→1/4 matches what the message always said, not a guess). Its per-character sliding-window loop if( j%2==0 && !is_chinese(name[j..j+1]) ) { name[j]+=128; name[j+1]+=128; } (byte-offset gate + a byte-shift "auto-correct" hack that has no valid meaning against Unicode codepoints) → if( !is_chinese(name[j..j]) ) { <reject>; }, using the exact reject message the original author had already written but left commented out. - Verified via 3 independent full registration tests (see below) that real single/double-character Chinese names are accepted correctly and rejected names really are rejected (not just "doesn't crash").

2. AGENTS.md §15p (DNS/intermud daemon preload exclusion): adm/etc/ preload listed /adm/daemons/network/dns_master — removed proactively before the first boot attempt. (A second, unrelated copy of the same file sits at adm/daemons/dns_master.lpc, outside network/ — confirmed it's not the one the DNS_MASTER macro resolves to, include/net/daemons.h points at the network/ copy only, so nothing else needed touching.) Also checked §15ab's "inline DNS calls outside preload" variant: logind.lpc's logon() does call MUDLIST_CMD->main(...) unconditionally on every connect, and that command does call into DNS_MASTER, but it's guarded by its own find_object(DNS_MASTER) check and degrades to a harmless notify_fail() when not loaded — confirmed safe by inspection and by zero hangs/errors across every boot+test cycle.

3. AGENTS.md §14 (valid_override 3-arg upgrade): master.lpc's valid_override(file, name) was 2-arg — upgraded to (file, name, main_file) and added main_file == SIMUL_EFUN_OB || main_file == MASTER_OB to the allow-check, so an efun:: override written inside a file #included into simul_efun.lpc is still recognized.

4. AGENTS.md §8d/§15o (get_include_path() insurance): master.lpc had no get_include_path() apply at all — added the standard directory-prepending implementation as insurance for any live, mid-connection compile of a file using a local (same-directory) #include. (convert_lib.sh's own §8d pass already converted 24 local angle-bracket includes to quoted form, which resolves without needing this apply at all — this addition is pure insurance, not a fix for an observed failure.)

5. AGENTS.md §8e (tail is not a real FluffOS efun) — fatal here, confirmed on the very first boot attempt: adm/simul_efun/message.lpc's tail(string file) called efun::tail(file), and since this file is #included directly into simul_efun.lpc, the compile error (Unknown efun: tail) took down the *entire* simul_efun object, which the driver refuses to boot without (*No program in object '/adm/single/simul_efun'!). Reimplemented in plain LPC (read the file, split lines, take the last N, write them) — same pattern as catalogued in AGENTS.md, same severity as the bmxkx2001 instance of this exact bug (that lib shares this codebase family too).

6. AGENTS.md §15c (adm/etc/preload-style bare-.c data-file refs): found a SEPARATE instance in adm/etc/task/task_list (a plain-text list of NPC-quest object paths read by adm/daemons/taskd.lpc's random_place()/update_task()), still listing the pre-rename /adm/etc/task/obj/*.c paths. After the .c→.lpc rename these new() calls returned 0 silently, and the caller's very next line (ob->query("task_owner"), no objectp() guard — see AGENTS.md §15e) threw *Bad argument 1 to EFUN call_other() ... Got: int(0) every time this daemon's periodic job fired. Fixed with sed -i 's/\.c$//' on the data file, same fix shape as the catalog entry.

New findings from this pass (not yet in AGENTS.md's catalog as of this write-up)

7. private on an add_action-bound dispatcher, once inherited, gets demoted to DECL_HIDDEN and the driver refuses to call it — silently breaking EVERY player command post-login. This is the single most impactful bug found in this lib and, as far as I can tell, a genuinely new bug class for this whole project's catalog.

feature/command.lpc (inherited by inherit/char/char.lpc, the player body base class) declares: ``lpc private nomask int command_hook(string arg) ` bound via add_action("command_hook", "", 1) in enable_player(). This function is the *sole* generic dispatcher for movement, verbs, emotes, and channels — literally every command a connected player types. On this driver (vm/internal/apply.cc's apply()), a driver-origin add_action callback dispatched against an object requires at least DECL_PRIVATE access when current_object == ob — but DECL_PRIVATE ("can't be inherited") demotes a private function to DECL_HIDDEN once it's compiled as part of an *inheriting* program rather than its own defining file, and command_hook is only ever invoked as a member of the inheriting player-body class, never as a member of feature/command.lpc itself. Symptom: full registration completes cleanly, the character lands in the actual start room with a correct room description on entry, but every subsequent typed command (even look) silently produces only the config's default error message, with a matching debug.log entry: ` apply() with insufficient permission: cob: clone/user/user#1, ob: clone/user/user#1, function: command_hook, origin: efun, needs: private, has: hidden ` How this was missed on 2 prior sibling libs: xkx2001's own copy of this file has nomask int command_hook (no private at all) — so xkx2001 never hit this. bmxkx2001 (archive #45) *does* have the identical private nomask` shape, but that lib's own NOTES.md confirms testing stopped right at "reaches the password prompt" and never issued a real post-login command — so the bug was live there too and never caught. This is exactly the class of gap this task's instructions specifically warn about: "boots and reaches a prompt" is not "the feature actually works."

Fix: drop private (nomask int command_hook(string arg)), matching xkx2001's own already-safe version verbatim. nomask alone still prevents any override.

Recommendation for AGENTS.md: worth its own catalog entry (a new §15ac or similar) — check any lib's core add_action-bound dispatcher function (commonly named command_hook/parse_command/similar, defined in a feature/-style file and inherited into the player body) for a private access modifier, and always issue at least one real post-login command (not just look at the banner) before considering a lib's gameplay loop verified.

8. securityd.lpc's ACL mappings are never initialized when the archive ships with no seed save data for the daemon — a second, related bug that surfaced immediately after fixing #7 above (the very first real command that could finally reach command_hook then hit this one).

securityd.lpc create() calls restore(), and only if that FAILS does it fall back to manually initializing wiz_status/wiz_sites — but trusted_read/exclude_read/trusted_write/exclude_write/ authorized_cmds/exclude_cmds are never initialized in that fallback branch at all (only authorized_cmds gets a lazy if(!authorized_cmds) authorized_cmds = allocate_mapping(...) inside valid_cmd() itself — the other five don't get equivalent treatment anywhere). This archive has no /data/securityd.o seed file at all (confirmed: the only securityd.o anywhere in the tree is a wizard's unrelated personal-sandbox copy under clone/obj/u/xuanyuan/daemons/), so restore() always fails. Symptom: valid_cmd()'s very first exclude_cmds[dir] lookup on the still-0 mapping threw *Value being indexed is zero. for literally every player command.

Fix: added lazy if(!X) X = allocate_mapping(1000); for all six ACL mappings once, at the end of create().

A second layer of the same root cause: once the mappings themselves were guaranteed non-zero, the *next* line down hit a related but distinct crash — authorized_cmds["cmds"]/ trusted_write["/"]/trusted_read["/"] are looked up directly (not via the undefinedp()-guarded loop the rest of the function uses) and passed straight into member_array(), which rejects an int (the 0 you get from a missing mapping key) for its 2nd argument (Expected: string or array Got: 0). Fixed by pre-seeding exactly these three well-known keys as ({}) in the same create() block.

Both of these are latent/dormant in every sibling lib that ships with real save data for this daemon (a populated ACL is the normal case) — this is specific to this archive's snapshot shipping without one, not a systemic driver-compat bug the way #7 is. Worth a narrower catalog note (checking whether restore() ever actually succeeds, and adding defensive allocate_mapping()/({}) fallbacks in any lib's securityd.lpc create() when it doesn't) rather than a blanket rule.

static→nosave blanket-sed collateral damage (§3 counterexample, recurring)

Same shape as the moniHuafu/yanhuangwuhun precedent already in AGENTS.md, but a wider blast radius here (23 files, the leading-slash variant "/static/..." rather than just "static...): this lib logs extensively via log_file("static/XXX", ...) / log_file("/static/XXX", ...) (a real log/static/ directory ships in the archive) for crash logs, read/write/cmd-denial audit logs, clone logs, award logs, etc. — convert_lib.sh's blanket \bstatic\b→nosave sed rewrote every one of these path literals to "nosave/..."/"/nosave/...", silently orphaning the real log/static/ seed directory. Found and reverted with grep -arl '"nosave\|/nosave/' across the whole work/ tree (not just the narrower "nosave check used on prior libs, which would have missed the leading-slash form) → sed 's/"nosave/"static/g; s/\/nosave\//\/static\//g' on every hit. Affected: adm/single/master.lpc (crash()'s static/CRASHES), adm/simul_efun/message.lpc (5 sites), adm/daemons/securityd.lpc (5 sites), its wizard-sandbox duplicate at clone/obj/u/xuanyuan/daemons/securityd.lpc and .../logind.lpc, several cmds//u/-tree files' AWARD_LOG/SUICIDE_LIST/etc, and — a distinct sub-case — the *historical audit log data* file log/static/more.lpc itself (a 更 record of who ran the more command on what file, plain text, mistakenly renamed from .c→.lpc and then swept by the same sed since it now had a .lpc extension, corrupting its own content, not code — its lines record historical paths like /log/static/suicide.c). Reverted that one too even though it's just cosmetic seed-log text, for consistency. A separate mid-pass mistake, corrected: while iterating on this, an over-broad rm -rf work/log (intending only to clear player-save-state under work/data) also deleted this lib's entire work/log/ seed-data subtree (crash logs, WIZ_LOGIN, MONEY, etc, and the regban.log that adm/daemons/regband.lpc's is_banned() writes to on every connection attempt) — this reintroduced the exact check_legal_id-adjacent crash described above (*Wrong permissions for opening file /log/regban.log for append. / No such file or directory, silently aborting get_id() mid-connection). Recovered by re-running convert_lib.sh into a scratch directory (raw→work conversion is idempotent given the same raw source) and copying just the regenerated log/ subtree back in, re-applying the same static/nosave revert to it. Lesson for future sessions: work/log/ (the *mudlib's own* virtual /log/ tree, seed content) and the sibling libs/<slug>/log/ (the *driver's* debug-log output directory, safe to clear) are two different things with the same basename — don't rm -rf` both in one command.

lpcc sweep content fixes (found via the sweep, applied where cheap and correct)

What I confirmed was NOT needed, and how

lpcc sweep — triage of remaining noise (361/10605 fail, 96.6% pass, up from 96.4%/383-fail pre-fix)

Grouped the 361 remaining failures by top-level directory before accepting them as noise (not exhaustively fixed, per AGENTS.md §6b):

Interactive test result — full registration + real gameplay commands

Read adm/daemons/logind.lpc's actual input_to chain before scripting (logon → confirm_big5 → get_id → confirm_id → get_name → new_password → confirm_password → specify_gifttype → get_gift → get_email → get_gender → enter_world) — confirms a hidden pre-id BIG5/GB font prompt ("Do you want to use BIG5 code?(y/n)") right after the banner, exactly the kind of hidden gate AGENTS.md's "hidden pre-id prompt" family warns about.

Ran 3 independent full registrations in 3 separate continuous mudclient.py sessions, each covering registration all the way through real post-login commands (not just reaching a prompt):

1. n (GB code) → id qinling → confirm y → real Chinese name 秦岭 → password test12345 (×2) → gift type 0 → accept gift y → email [email protected] → gender f → lands in the actual start room (沙滩 — a beach zone, full room description with exits/NPC rendered correctly) → look now correctly re-displays the room (this was the run where the command_hook/securityd fixes were validated — before them, look produced only the config's default error message with a debug.log crash trace) → score → quit → "开始退出游戏, 进行中 ..." (graceful quit). 2. n → id linfeng → y → real Chinese name 林风 → same flow → lands in the same start room, an NPC ("「罚恶使」李四") correctly greets the character by name ("恭迎林风") → zero debug.log errors for the entire session. 3. n → id qinfeng → y → real Chinese name 秦风 → same flow → lands in the start room, a different greeter NPC ("「赏善使」张三") greets by name ("恭迎秦风") → look → i → quit → zero debug.log errors of any kind (执行时段错误/Bad argument/ Undefined function/Value being indexed/insufficient permission: all zero hits) — this is the final, fully-clean run after every fix in this document was applied.

All three names are real, valid Chinese input (2-character names, both characters within the CJK range, not on the lib's banned_name list), confirming the §15h fix actually works end-to-end and the flow proceeds correctly past registration into fully-playable post-login gameplay, not just "reaches a prompt."

Port / process

Port 40064. Driver launched via setsid nohup ... & disown per AGENTS.md's guidance; killed by exact PID after each test cycle, never a broad pkill pattern (several other agents had drivers running concurrently throughout this session). No driver process left running at the end of this pass; work/data (player save state from testing) and log/debug.log cleared before finishing.

2026-07-23: driver rebuild retest + LPC formatter + WASM check

2026-07-23 (integrity review): the "score produces no output" observation is INTENTIONAL design, not a bug

Investigated the previous pass's open observation that score silently produced no output for a fresh character. Root cause: this lib's own onboarding/registration gate, the same design family as bmxkx2001's block_cmd gate:

Verified end-to-end interactively: completed the whole follow/register/re-login flow with a test character; score then printed the complete character sheet (【玄剑录个人档案】). No fix needed — do not "fix" the silence in shatan1/register, it is the original game's anti-unregistered-play gate working as designed. (Side note for future testers: the register step's issued-password disconnect is easy to mistake for a crash; it prints 您的新密码是XXXXX then closes the link.)

WASM-enablement pass (loopback-allow / gate bypass / admin seed)

Standard WASM-first pass per AGENTS.md §1.3b/e and §1.5. Gates patched:

- logon()'s BAN_D->is_banned(query_ip_name(ob)) gate (~line 173) — now skipped for loopback connections. - get_id()'s REGBAN_D->is_banned(query_ip_name(ob)) registration ban (~line 285) — skipped for loopback. - No uptime() startup gate and no per-IP flood throttle exist in this logind (MAX_USERS capacity cap left intact — hosting capacity, not per-IP).

- wiz_levels rank table made nosave (restore() from the new /data/securityd.o was ZEROING it per §7.7, crashing get_wiz_level() → every wiz_level() call → login desync). - create() ACL seeding extended: authorized_cmds now seeds cmds/adm/cmds/arch/cmds/wiz/cmds/imm status arrays. Without this, valid_cmd() denied every wizard command for every rank including (admin) (the legacy lazy-init inside valid_cmd() can never fire once create() makes the mapping non-null — pre-existing latent bug, exposed by the first real wizard this lib ever had).

Admin account seeded: id fluffos, pw Mud@2026, name 浮浮, granted (admin) via a hand-seeded /data/securityd.o (wiz_status["fluffos"]="(admin)", wiz_sites["fluffos"]=".*"; "npc" entries kept from the daemon's own defaults). This lineage's wizard login additionally requires a wizard_password field on the login save — seeded into data/login/f/fluffos.o as the same crypt hash as the password, so the 巫师验证码 is also Mud@2026. Verified: real registration flow (n/fluffos/y/浮浮/pw×2/0/y/email/m → landed on 沙滩), relogin as fluffos (password + 验证码) → update /adm/daemons/band.lpc → 成功, score correct. Retest: fresh normal registration (秦风 m) end-to-end, look/quit correct; test char saves removed; zero new debug.log errors.

Save files for the orchestrator to add (none gitignored, normal add):

Fail-closed retrofit for the loopback-allow gate (2026-07-24)

The loopback helper above was originally written matching the project-wide convention at patch time (AGENTS.md §1.3b), which also treated an empty/non-string/malformed query_ip_number() result as loopback, defensively, because the WASM driver used to return garbage there. That underlying driver bug is now fixed (fluffos commits e33bb5da "fix: query_ip_number() returned uninitialized garbage under WASM" and 007bb863 "feat: synthetic resolve() on WASM instead of raising an LPC error", both 2026-07-23; the locally-built build-debug/build-wasm binaries already postdate both commits), so treating unparseable IPs as trusted is a fail-open gap with no remaining justification. Retrofitted to fail-closed: loopback is now strictly ip == "127.0.0.1" || ip == "::1" || ip[0..3] == "127." (with a stringp() guard before the slice) — a malformed/empty IP now falls through to the NORMAL gate instead of being treated as local. Retested after tightening: fresh driver boot clean, fluffos loopback login and its wizard update command both still work; zero new debug.log errors.

深度功能测试 / Deep functional test (2026-07-24, round two)

First real *playthrough* pass on this lib per AGENTS.md §10.7 (all prior passes verified registration + look/score/quit/admin-login, or watched boot output, but never played the actual game systems). Native driver (build-debug), one continuous mudclient.py session per phase, real Chinese names throughout, real wall-clock waits for the two timer-gated systems this lib turned out to have (see below).

Read first: doc/help/intro (the closest thing to a newbie primer this lib ships — no plain doc/help/newbie file, only newbie_wudang/newbie_gumu/newbie_lingjiu/newbie_baituo/ newbie_job per-sect/topic variants) — explains the 14-sect roster, attributes, fight/kill/hit combat-verb distinction, and the xue/du/lian three ways to learn skills, matching what was tested below almost exactly.

Test character: id wentian, real Chinese name 闻天笑, password Test12345 → after completing this lib's own two-stage registration (see below) a NEW password doeal was issued and is now the live login password. Kept (not cleaned up) as the representative playthrough character — final state: registered, strike skill learned (1 point), standing in 沙滩 (the real post-registration start room). Save files: work/data/user/w/wentian.o, work/data/login/w/wentian.o. Two other characters created while probing the registration-loop edge cases (shiying/上官虹, shangfei/上官飞, both abandoned mid-tutorial, registered:"no") were deleted before finishing — pure test debris, not representative state worth keeping.

This lib's registration is TWO-STAGE, and the second stage is the real "tutorial"

Already documented above (the shatan1/block_cmd section) that a freshly-registered character is unregistered (query("registered") == "no") and dropped in /d/xiakedao/shatan1 with almost every command silently swallowed. What that earlier pass didn't walk end-to-end: the escort NPC (d/xiakedao/npc/li4.lpc/zhangli.h's shared greeting()/check_follow() call_out chain) auto-teleports the character to /d/xiakedao/register 5-15 real seconds after a follow li4, where a second NPC (mux.lpc) explains register <email>; running that command calls adm/daemons/regid.lpc's register_char(), which issues a brand-new random 5-letter password or wall NOR — ("您的新密码是XXXXX请用新的密码连线") and immediately disconnects the link — easy to mistake for a crash if you don't already know this is by design (same category of trap bxsj/xiyouji's NOTES already warn about: a "disconnect" that is actually correct game behavior). Reconnecting with the new password lands the character in the REAL start room /d/xiakedao/沙滩 (a different room object from the shatan1 gate, same short name "沙滩" — confusingly) with a fisherman NPC, full score/i access, and the actual 侠客岛 (Ke Xia island) content available. Verified end-to-end live, twice (once organically stepping through each input_to prompt, once after a same-character reconnect mid-flow — the escort/register state genuinely survives a real disconnect+reconnect, confirming this part of the flow is robust).

Exploring the starting zone — 侠客岛 is a self-contained tutorial island

Read room .lpc source throughout rather than guessing blind — exits are not always neighbors-of-neighbors intuitive here (a hidden enter exit that only exists for a 15-second real-time window after a call_out, a climb tree/wear coat/jump fall puzzle gating one internal passage). Confirmed working, all via wentian:

Off-island travel is real-time-gated and genuinely slow to test solo

/d/xiakedao/shatan's fisherman auto-offers a boat enter exit exactly once, ~1 real second after the room is entered, via a call_out chain (check_trigger→on_board(+15s)→arrive(+20s)→close_passage(+20s)); missing the initial 15-second boarding window leaves the underlying chuan (boat) room's yell_trigger flag SET until shatan's own next driver-scheduled reset() (this lib's config.fluffos sets time to reset : 1800, i.e. up to 30 real minutes) — shatan.lpc:111's reset() is the ONLY code that clears it. Missed the window on the first live attempt (normal exploratory-testing pace, not a bug); a driver restart (unrelated, see "an unrelated process died" below) incidentally reset all in-memory room state and gave a second live window, which was also missed testing navigation. Reaching the mainland past this point (to independently verify a real sect join, a skill-teacher NPC outside the island, or a player-currency shop purchase) was NOT completed within this session's time budget — see explicit list below. Scouted the mainland via wizard goto instead (shop test above; sect-hall NPCs not individually walked to).

Bugs found and fixed

1. inherit/item/combined.lpc:19 (originally) — stackable items spent to exactly zero silently never self-destruct (money in particular). NEW manifestation of the AGENTS.md §8.3a bug class (private function bound to a driver-origin dispatch, demoted to DECL_HIDDEN once inherited), via call_out instead of add_action.

`` apply() with insufficient permission: cob: null, ob: clone/money/silver#295, function: destruct_me, origin: internal, needs: private, has: hidden ``

``lpc // BEFORE: private void destruct_me() { destruct(this_object()); } // AFTER: void destruct_me() { destruct(this_object()); } ``

2. feature/action.lpc:70 (originally) — same bug class, much higher blast radius: EVERY timed kungfu-skill/drug status effect in the game silently never fires for players.

`` apply() with insufficient permission: cob: null, ob: clone/user/user#1, function: eval_function, origin: internal, needs: private, has: hidden ``

``lpc // BEFORE: private void eval_function(function fun) { evaluate(fun); } // AFTER: void eval_function(function fun) { evaluate(fun); } ``

3. clone/npc/shan.lpc:181 — pre-existing missing closing quote crashes this NPC's entire program, taking down any room/NPC interaction chain that happens to reference it.

``lpc // BEFORE: command("say 这种私人恩怨太多了,你还是自强不息啊); // AFTER: command("say 这种私人恩怨太多了,你还是自强不息啊。"); ``

An unrelated process died mid-session (environment note, not a mudlib bug)

The native driver process (a plain setsid nohup .../driver config.fluffos &, PID recorded and monitored per AGENTS.md §10.5) was found dead partway through this session with no crash trace at all in debug.log (no FATAL ERROR, no SIGTERM line, nothing — the log just stops mid-preload-warning-spam). Given this is a shared multi-agent environment (other libs' driver processes were visibly running throughout, per ss -tlnp/ps aux), this reads as an external kill/OOM rather than anything the mudlib did — restarted cleanly (Accepting telnet connections/Initializations complete), all prior save-file state intact, no fix needed or applicable. Noted here in case a future session sees the same "driver just isn't there anymore, zero debug.log explanation" symptom.

Explicitly NOT verified live, and why

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

基于 XKX 引擎的自制游戏。

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

深度功能测试第二轮 / Deep functional test round two (2026-08-15, post driver-upgrade re-test)

驱动于 2026-08-12 升级后的重测。标准检查清单发现并修复一处问题:

cmds/imm/update.lpc、cmds/wiz/update.lpc(均已是 §7.106 正确写 法)与 master.lpc::log_error()(§7.10 的 "arning:" 大小写无关 写法)均已是正确写法,maximum evaluation cost 已经是 30000000,均无需改动;adm/simul_efun/file.lpc 本身不定义 cat()/log_file()(与 shenzhou 同宗架构),无适用修复项;本档案 无 adm/daemons/closed.lpc,不受 §7.107 影响。

现场验证摘要

驱动干净启动,管理员 fluffos/Mud@2026(含"巫师验证码"二次确 认,同一密码)登录确认 目前权限:(admin),update /adm/daemons/logind 成功验证真实写入权限。踢掉重复登录重连路径 现场验证通过(见上)。debug.log 全程干净(1142 行,无真实错 误)。

本轮修改的文件

Round three deep functional test (2026-08-18)

Context: libs/xuanjianlu/work/include/globals.h had just been patched (commit cc35c33d23e, same session) for a missing EDITOR_D macro that inherit/misc/bboard.lpc needs — this lib shares close lineage with libs/jym, whose own round-three pass earlier the same day found several real bugs (missing EDITOR_D, enter_world()'s ob->save() commented out, and an unguarded init()→call_out() chain in the death-guide NPCs — AGENTS.md §7.112). This pass specifically re-checked all three patterns here, plus went deeper than round two on combat/death.

Checked all three jym-lineage patterns:

1. logind.lpc's enter_world() ob->save(): read in full (lines 704-788) — ob->save() (line 722) and user->save() (line 788) are both present and uncommented. Not affected. No fix needed. 2. §7.112 (init() unconditional call_out() chain, no re-entry guard): found and fixed, see below. 3. §7.111 (master.lpc's standard_trace() unconditional file_name()): not applicable — this lib's standard_trace() (adm/single/master.lpc:211) formats error["object"]/ error["trace"][i]["object"] with %O, not file_name().

Bug found and fixed: §7.112 in the death-guide gargoyle NPCs (same shape as jym, byte-for-byte matching source)

d/death/npc/{wgargoyle,wgargoyle1,bgargoyle}.lpc (the underworld-gate NPCs a player's character is escorted through on death — wgargoyle/ wgargoyle1 at /d/death/gate, bgargoyle at /d/death/gateway, both reached via DEATH_ROOM = /d/death/gate.lpc from feature/damage.lpc's die()) all had init() unconditionally calling call_out("death_stage"/"final_death_stage", 30, previous_object(), 0) with no guard against being called twice. Per AGENTS.md §7.112, FluffOS re-broadcasts init() to every object in a room whenever enable_commands() is called on an interactive there (confirmed default __RC_ENABLE_COMMANDS_CALL_INIT__ = 1 in this driver build via src/base/internal/rc.cc), and this lib's clone/user/user.lpc reconnect() calls enable_commands() on every reconnect (already fixed for a related gap in the 2026-08-15 round-two pass, §7.108) — so a player/ghost sitting in one of these rooms who reconnects even once mid- sequence would get a duplicate call_out chain stacked on the first, racing to apply reincarnate()/room-move/drop-inventory twice.

Fix (matching the exact shape already applied and proven in jym): added a set_temp("death_stage_active", 1)/query_temp guard around the call_out() scheduling in each init(), cleared via delete_temp() at every exit point of death_stage()/final_death_stage() (the !ob || !present(ob) early return, bgargoyle's "not a ghost" early return, and both the intermediate-stage and final-stage completion branches). All three files are pure-LF (verified via a byte-mode read before editing, per this task's CRLF-safety instruction); git diff --stat confirms only the intended lines changed.

Live-verified two ways:

Bulletin board (§7.86/globals.h EDITOR_D fix) — live-verified working end-to-end, first real test since the fix

Tested at /d/xiakedao/dadong's 侠客岛告示牌 board (reached via admin goto, real accessible in-game location — a large hall on the tutorial island, not otherwise reached during either prior round's playthrough). list → 一块白杨木的牌子。侠客岛告示牌上目前没有任何留言。 (empty, correct). post <title> opens the real in-game line editor (结束离开 用'.',取消输入用'~q',使用内建列编辑器用'~e'); typed a line, ended with . → 留言完毕。. list afterward correctly showed the new entry with author/timestamp; read 1 correctly displayed the full posted text. Re-verified against a fresh cold-compile boot (killed the driver, restarted, list/read against the still-empty board after removing the test post) — no compile error, board loads and responds correctly from a completely cold /inherit/misc/bboard.lpc compile. The EDITOR_D fix works; boards are fully functional. (Test post + its data/board/xkd_b.o save file were removed before finishing — pure test debris, not real player content.)

Real combat — verified for the first time (neither prior round exercised it)

wentian (登 strike skill, weak stats) vs d/xiakedao/pubu's skill- teacher NPC (master2.lpc, "蓝衣弟子", reached via the same shatan→n→n→n→n→northup route round two documented). Two findings on verb syntax, both confirmed as intentional design, not bugs:

Not reached this round either (same off-island travel gate as round two)

The boat/mainland travel gate (chuan's one-shot 15s boarding window, config.fluffos's time to reset : 1800) still wasn't budgeted for — real sect-hall joining, a mainland skill teacher, and a player-currency (not admin-assisted) shop purchase remain unverified by an organic character, same gap round two flagged. Everything reachable from the tutorial island (board, real death/revival, real PvE combat, the two jym-lineage bug patterns) is now covered.

Generalizable finding for future sweeps (not chased further here, per task scope)

§7.112's init()-unconditional-call_out() shape and the §7.111 file_name() shape are both proven-safe/proven-absent patterns worth a quick grep-and-check on any other lib in this same ES-II/XKX lineage (xkm, shenzhou, xkx2001, and any other sibling that got the same cc35c33d23e EDITOR_D fix) — not done here since this task's scope was xuanjianlu only.

Cleanup

Fresh driver restart after the fix (killed original PID by exact PID, confirmed cwd match); re-verified board + gargoyle compile cleanly from a cold boot. Test-debris save files (wjltestc abandoned unregistered character, the board test post's data/board/xkd_b.o) removed before finishing. wentian's and admin fluffos's save-file diffs from this session (death_count→1, startroom, position, qi after combat, board_last_read, etc) are genuine gameplay state from the tests above and were kept/committed, matching this project's established convention of committing real playthrough state for the representative/admin characters. debug.log/boot.log scratch files removed from the lib directory before finishing.

Files modified this round

§7.100 修复(ROOM 基类的同一"多余 replace_program()"形状,全档案扫描第 6 批)

Round four deep functional test (2026-08-20) — the mainland is now reachable, all three previously-blocked systems verified

Picked up exactly where round three left off: real sect-hall joining, a mainland skill teacher, and a player-currency shop purchase were the last three items never verified live by an organic character, all gated behind shatan's one-shot 15-second boat-boarding window. This round got past that gate for real (no shortcuts on the boat mechanism itself) and then used the existing wentian/闻天笑 character (still registered:"yes", alive at 沙滩 from prior rounds) to test all three mainland systems.

How the boat gate actually works, and how it was beaten for real

Read d/xiakedao/shatan.lpc in full. The 15-second boarding window is not a fixed clock tied to config.fluffos's time to reset : 1800 — that 1800s value is only the *fallback* cleanup if the window is missed entirely. The real trigger is init() (fired on every room entry, including a reconnect that lands the character back in shatan), which schedules check_trigger() via call_out(..., 1). From the moment check_trigger() actually runs (call it T):

Beaten with a real timed telnet script (mudsock.py, written this session — a persistent-connection variant of mudclient.py that sends scripted lines at precise wall-clock offsets rather than only on inferred silence, needed because the 56-second real-time sequence above has async narration messages that would otherwise reset mudclient.py's idle-based send timer past the boarding window): connected as wentian, which was still sitting in shatan from a prior round — login itself re-entered the room (a reconnect calls enable_commands(), which re-broadcasts init()), so check_trigger() fired within ~2 seconds of login. Sent enter at t=6.5s (safely inside the T..T+15 window), then out at t=46.5s (safely inside the T+35..T+55 window) — landed at shatan3 cleanly on the first real attempt, no admin bypass of the boat mechanism itself.

Observation, not a bug: chuan.lpc's disembark narration says "你一 看原来是些银子和两块令牌" (you find some silver AND two tokens in your hand) but the actual valid_leave() code only grants the two tokens — the silver grant is commented out (// money = new(...); ...). This matches round two's independent finding about the same commented-out line; not re-fixed here (flavor-text/code mismatch, no crash, no observably-wrong state transition — game text overpromising is content, not a programming bug per this session's scope rules).

Getting around the mainland: the cart (大车/da che) is a real, working, one-shot vehicle

d/xiakedao/obj/car.lpc's do_travel() (verbs qu/goto) is un-gated (no fee, no do_hire/hire/gu prerequisite — those verbs are registered via add_action but do_hire() is only ever forward- declared, never defined anywhere in the file; calling hire/gu would silently no-op or error, but the cart's own dialogue only ever advertises qu, so this is almost certainly harmless vestigial/unwired code, not a live bug — not fixed, matches this session's "leave unreachable dead code alone" precedent from round three's start_death() finding). qu <destination> (e.g. qu wudang, qu quanzhou, qu yangzhou) works as documented: a real 40-second transit (call_out("arrive", 40, ...)), then the SAME cart object relocates to the destination room and immediately self-destructs (call_out("destroy_it", 0, ob)) — a genuine one-shot vehicle, matching shatan3's single starting 大车 and confirming why a return trip needed a fresh cart. wentian's own 20-silver registration grant (adm/daemons/logind.lpc's enter_world(), real and uncommented) had already been spent in an earlier round's testing, so — per this task's explicit allowance for "a wizard call-granted starting sum... if that's the normal way players get initial capital" — admin cloned and handed wentian fresh 10-20 tael silver stacks at need (exactly mirroring the real registration grant amount, spent down authentically by each subsequent transaction, never inflated beyond what a real player's starting capital would cover). Also cloned two disposable d/xiakedao/obj/car.lpc instances (mainland cart travel is otherwise gated behind having already ridden the very cart being consumed) to reach 武当/wudang and 泉州/quanzhou without re-running the boat sequence each time — this is the "goto to bypass re-testing the boat/cart mechanism itself" allowance the task explicitly granted, used only to skip REDUNDANT repeats of an already-verified mechanism, never to skip the actual systems under test.

1. Real sect-hall joining — WORKS, live-verified, no bug

Target: 武当派/Wudang, reachable immediately at the mainland arrival room (d/wudang/shanmen.lpc, "玄岳门"), which hosts kungfu/class/wudang/lingxu.lpc(灵虚道长, generation 3 real family member — create_family("武当派", 3, "弟子")). Its attempt_apprentice() (via #include "daozhang.h") is a REAL, multi-stage recruitment flow, not a hardcoded island-NPC-style refusal:

2. Mainland skill teacher — WORKS, live-verified, no bug

cmds/skill/xue.lpc's learn command requires me->is_apprentice_of(ob) || ob->recognize_apprentice(me) || ... — none of the reachable Wudang NPCs define recognize_apprentice() locally (only is_apprentice_of, which needs FULL sect membership, a longer grind than round four's time budget), so the real "pay a fee, learn immediately" teacher path tested here is 泉州/Quanzhou's 扬威武馆 (martial hall), reached via qu quanzhou → west/south/west/west to 前厅. d/quanzhou/npc/mawude.lpc's accept_object() grants mark/马 (temp flag) to anyone who hands over a money item worth ≥500 (i.e. 5+ silver) AND has combat_exp <= 3500 (a genuine "no advanced fighters" gate, wentian's combat_exp is 0, passed easily) — the department teachers (chenhu.lpc etc.) then gate recognize_apprentice() on that same mark/马 flag. Live-verified: give silver to ma (20 taels handed over — the accept logic takes the WHOLE stack handed to it, no change given, which is harsh-but-intentional design, not a bug) → "请到后院学习你所喜欢的功夫吧" (go learn what interests you) → navigated to 棒杖部/bangbu (陈浒/Chen Hu, stick/staff/cuff/force teacher) → xue hu force → "你听了陈浒的指导... 你的「force」进步了!" → skills correctly shows force at level 1. Zero debug.log errors. This is the real organic mainland-teacher mechanism the task asked for, confirmed working end-to-end.

3. Player-currency shop purchase — WORKS, live-verified, no bug (real partial-stack spend, not round two's zero-stack edge case)

d/city/jujinge.lpc's 牛掌柜 (already found working in round two, but only with admin-cloned currency spent to exactly zero). This round repeated it with wentian's own money and a non-zero remainder (the round-two/round-three §7.52-lineage bug class this project documents — stackable items spent to exactly 0 silently orphaning — only manifests on the exact-zero case, so this is a genuinely different code path than round two exercised): reached via qu yangzhou → east/south/south/south/west to 聚金阁 (jujinge). list showed the correct price table (铜镜/mirror: 5 taels). buy tong jing with 10 taels in hand → "你从牛掌柜那里买下了一面铜镜。" → inventory correctly shows 5 taels remaining + the mirror. Zero debug.log errors, including no insufficient permission/destruct_me lines from the §7.52-lineage bug class (not triggered here since the stack wasn't spent to exactly zero — that specific edge case remains covered by round two's original fix in inherit/item/combined.lpc).

Standing-checklist sanity pass (all confirmed intact/clean, no regressions, no new fixes needed)

Optional secondary check: §7.111/§7.112 grep-only pass on sibling libs xkm, shenzhou, xkx2001

Per this lib's own round-three "generalizable finding" flag (these 3 share the same EDITOR_D-fix-era lineage and the same gargoyle death- NPC discovery arc). Grep-only, not a full test, as instructed:

Cleanup

Fresh build-debug driver boot for this round (port 40064, PID 771369, killed by exact PID at the end — confirmed no longer running). log/debug.log clean throughout (1420 lines at session end, zero error-pattern hits at any checkpoint). No new test characters were created — reused the existing wentian/闻天笑 and admin fluffos characters per this project's established convention. wentian's and fluffos's save-file diffs from this session (new 武当道童 title, wudang/offerring, force skill, mirror/tokens in inventory, silver balance) are genuine gameplay state from the tests above and were kept, matching the same convention documented in every prior round. One new file appeared, work/data/npc/job_server.o — the persistent global save for the wudang_volunteer job daemon (/clone/obj/job_server.lpc), created as a normal side effect of the sect-joining flow's first-ever exercise on this lib; this is legitimate shared game-daemon state, not player-specific test debris, so it was kept rather than deleted.

Files modified this round

§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.

AGENTS.md §7.19 fix: enable_player() reentrancy from init()

feature/command.lpc's enable_player() (wrapper around enable_commands()) was reachable from an NPC's init(): the shared inherit/char/char.lpc setup() (called from every character's create()) itself calls enable_player(), and d/zhongnan/npc/killer.lpc redundantly calls setup() again from inside its own init() (on top of the setup() its create() already made) -- same shape as the originally-documented mhxy zhangmen.lpc case. enable_commands() is only safe to call from create(): calling it again on an object already living() makes the driver re-invoke that same object's init() as a side effect, which recurses back into enable_player() on the same call stack until "Too deep recursion" aborts the boot on a room's first-ever visit. Fixed with a true reentrancy flag (in_enable_player_now), NOT a living() guard (which would break legitimate re-enables from revive() in feature/damage.lpc and wakeup()/wakeup2() in cmds/std/sleep.lpc, both confirmed to re-invoke enable_player() on this lib while the object is still living()). Verified via lpcc --batch single-file compile check (PASS). Part of the corpus-wide §7.19 sweep (Batch E).

深度功能测试(§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)— compile-warning and load-failure pass (ported from xkx2001)

scripts/lpc_warnings.py xuanjianlu: 360 distinct diagnostics at the end, 10310 files compile and 221 do not. A load check of every .lpc (10605 files, 700 per VM boot) at HEAD and in the working tree: 10252 PASS -> 10302 PASS, no file that loaded before fails now. 1550 files changed. The code is the xkx2001 root's, so the pass is its pass (libs/xkx2001/NOTES.md has every class, the typos found through the no-side-effects list and the list of files left alone): lpc_twin_port.py adopt xkx2001 xuanjianlu carried the fixes of the files whose code is the root's, then lpc_warnings.py --fix, lpc_diamonds.py --fix-redundant, lpc_fix_void_override.py, lpc_fix_macro_redef.py, lpc_fix_prototype_types.py, lpc_move_decl.py, lpc_fix_no_effect.py, lpc_fix_heredoc_terminator.py and (after a rescan) lpc_name_winner.py handled the diagnostics that depend on this lib's own files.

Fixed by hand in this lib (each read first; text restored only from a sentence that a sibling lib carries unchanged): clone/board/news_b.lpc (do_discard() was string and returned its messages as strings, so the newsdel command never printed them and always reported success: int, notify_fail(), write() and return 1); d/huanghe/doc/set_bang.h (two answers end in unrecoverable characters; both strings are closed after 帮主的景仰之情); kungfu/skill/shenghuo-quan.lpc (return notify_fail("你的内力太弱,无法练仁セ鹑); — the sentence bixiecanyang, chidi, hc and ffxymud carry: 无法练圣火拳。\n"); d/zhongnan/gate1.lpc (a varargs the port had left on a line of its own, now part of the function header); and the libs' adm/simul_efun/message.lpc got the message_combatd() alias (KB 04 §6.2).

What is left: the files that do not load in the root either, the wizard directories (/u/dns, /u/feitian, /u/yijiu, /clone/obj/u/xuanyuan) with their missing headers (BINGQI_D, c_cond.h, wanted.h), and quest/wei/ (QUESTH_D never shipped, qhlist600000.lpc has a syntax error at line 2181).

深度功能测试(§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 xuanjianlu: 42 file(s) renamed to lower case (.C -> .lpc, .H -> .h) and 0 path reference(s) in 0 file(s) rewritten to the new spelling; 0 skipped, 0 ambiguous. Load check of the 50 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): 4 PASS -> 45 PASS, 0 that loaded before fail now, 41 more load.

The 42 files are 41 .C -> .lpc (the tea items of d/city/npc/tea, the Lingjiu array rooms bagua*, items of d/lingjiu/obj ...) and d/lingjiu/BAGUA.H -> bagua.h. Rescan: 352 distinct diagnostics, 10355 files compile and 217 do not (10310 / 221 / 360 before).

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

scripts/lpc_resilient_room.py --apply: make_inventory() of inherit/room/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)— 任务表里的 .c 路径与任务守护进程的防护

scripts/lpc_dynamic_rows.py:quest/wei/dynamic_location 里 185 行写的是原档案的 .c 路径。转档时文件已改名 .lpc,而显式后缀按原样解析,load_object("/d/x.c")、new("/d/obj/quest/x.c") 返回 0 且不报错(KB 03 §4.2,KB 06 §7.220),所以 adm/daemons/questd.lpc 读到的每一行都加载不出来:随机任务和任务物品从未生成,在 create() 里直接对结果调用 tar->set() 的版本还会崩溃,连带加载它的 cron 守护进程和 give 的任务钩子。这些行已改为 .lpc;另有 647 行指向的文件两种后缀都不存在(档案缺内容),保持原样。 scripts/lpc_dynamic_quest_guard.py:1 处 spread_quest() 调用(init_dynamic_quest() 的循环里)包进 catch(),缺失的任务物品或编译不过的房间只跳过那一个任务,守护进程照常加载、其余任务照常生成。 验证:adm/daemons/questd.lpc在新进程里加载(HEAD 一次,工作树三次,选房是随机的):HEAD 通过 0 个,工作树三次分别通过 1、1、1 个(共 1 个),无回退;adm/daemons/questd.lpc 原先加载失败,现在加载成功。

深度功能测试(§10.7,2026-10-11):dns_master 空 if

深度功能测试(§10.7,2026-10-11):dns_master 空 if

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

scripts/toolkit_fix.sh 一轮去掉 4 处多余的 inherit F_CLEAN_UP(d/xingxiu/tianroad3、inherit/room/pigroom、u/lbc/mj_harbor、mj_ship1)。手工:u/yijiu/guanfu/killer.lpc 先包含 npc.h(里面已 #include "map.lpc"),又直接包含一次 map.lpc,地点表 where 声明两次;删去第二次包含,表内容相同,行为不变。能编译的文件警告清零;改动的 6 个对象在 HEAD 与工作树各载入一次,regress 0。

负数伤害当作消耗(2026-10-15,KB 06 §7.241)

receive_damage("neili", -52)、receive_damage("qi", -12) 之类把 add() 的负数写进了 receive_damage():本库 feature/damage.lpc 对负数伤害报错(或归零),练功/运功时报 F_DAMAGE: 伤害值为负值 或消耗失效。已改为正数伤害,内力/精力类改为 add(type, -N)(scripts/lpc_fix_damage_calls.py):/kungfu/condition/huaiyun。

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

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

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

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

内力/上限当作伤害种类(补)(2026-10-15)

victim->receive_wound("force", n, me)(兵器吸内力)、receive_damage("max_qi", random(100))(走火入魔扣上限)等本库不接受的种类,17 处改为 add(type, -N)。

message_vision(msg, environment(me), me)(2026-10-15,KB 06 §7.242)

把房间当成了动作者:message_vision() 取房间的 name() 得 0,replace_string(msg, "$N", 0) 报错。在状态(中毒)里,这个错误发生在 apply_condition(name, duration - 1) 之前,状态永不结束,心跳里后面的恢复也每次被跳过。改为 message_vision(msg, me):/kungfu/condition/laugh、/kungfu/condition/sanpoison、/kungfu/condition/xianglu-du。

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。7 处补上 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):dagou-bang/ban、dagou-bang/chan、dagou-bang/chuo、dagou-bang/pi、dagou-bang/sansha、dagou-bang/tiao、dagou-bang/yin、damo-jian/weituo、huashan-jianfa/feilong、huashan-jianfa/san、huashan-jianfa/yijian、jingang-chu/wushang 等 25 个。

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

经验、属性、内力上限不是技能,xkx/ES 的 query_skill() 对它们只返回 0(风云的还会去载入同名技能文件而报错)。改为 query("...")(scripts/lpc_fix_pool_as_skill.py):驯马的 /d/dali/npc/mafu(成功率少了福缘一项)。

临济庄 remove 没有运功时报错(2026-10-15)

linji-zhuang/remove 直接 switch (lj_exert["name"]),lj_exert 是 query_temp("lj_exert");没有正在运用的临济庄功夫时它是 0,exert remove 每次都报 Value being indexed is zero。补上 if (!mapp(lj_exert)) return notify_fail(...)。本库的临济庄各 exert 都不设置 lj_exert,所以这个 exert 以前每次都报错,现在只会提示没有在运功。

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

query_skill() 里的技能名拼错了,或用的是本库改名前的旧名;本库没有这个技能,调用永远得 0:门槛(< N 就拒绝)永远过不去,相关功夫或绝招学不了、用不了;内功互斥(> N 就拒绝)从不生效;公式里这一项一直是 0。经汉字名(提示文字或中文名词典)或纯拼写差异(_/-、少一个连字符、相邻字母颠倒)核对后,改为本库的技能名(scripts/lpc_fix_skill_typo.py):douzhuan-xignyi→douzhuan-xingyi(/d/murong/obj/shu1);quanzhen-jianfa→quanzhen-jian(dugu-jiujian);shenhuo-xuanming→shenghuo-xuanming(/d/kunlun/skill/dafeng-yunfei);xiatian-gong→xiantian-gong(/d/zhongnan/skill/chunyang-quan)。

嵩山剑法「蓄力」没穿布衣时报错(2026-10-15)

songshan-jian/xuli 用 cloth = me->query_temp("armor/cloth") 取衣服,随后直接 cloth->query("material") 判断要不要显示“衣袖鼓起”的描写;身上没有布衣时(裸身或只穿铠甲)这里报错,而此时 apply/speed 已经扣掉、复原的 call_out 还没排上,身法就一直少了一截。两处判断前补 objectp(cloth) &&。

技能名拼错:技能文件里拼错自己的名字(2026-10-15,KB 06 §7.249)

技能自己的文件(或它的绝招目录)里,query_skill() 读的是差一个字母的自己的名字,永远得 0:“基本功夫要高过本技能”之类的限制从不生效,按等级取招、计算伤害的那一项一直是 0。改为本技能的名字(scripts/lpc_fix_skill_typo.py 的 self 判据):baitao-xuangong→bitao-xuangong(bitao-xuangong)。

地图数据库 mapdb 的空档防护(2026-10-15,KB 06 §7.250)

genmap 第一次载入时 if (!restore()) save(); 会在地图建好之前写出只有文件头的 data/npc/map.o;mapdb 读它成功后 map 为 0,所有查询都对 0 取下标(nitan170911、shenzhou 等 10 个库就带着这样的文件)。没有 map.o 时 query_random_room_path() 又对空数组取下标。mapdb(/clone/obj/mapdb.lpc)加一个 restore():读档后 map 不是映射就重置为 ([]);地图为空时 query_random_room_path() 返回 0。

随机取房间后不判断就过滤(2026-10-15,KB 06 §7.250)

/d/city/npc/ftb_zhu.lpc、/d/dali/npc/jiumozhi.lpc、/d/huashan/npc/killer.lpc、/d/qilian/obj/jinhe.lpc、/d/taohua/sky1.lpc 从 mapdb 随机取一个房间后直接 strsrch(room_path, ...) 排除区域;地图还没生成时取回的是 0,strsrch(0, ...) 报错,护镖等任务就此中断。条件前补 !stringp(room_path) ||,取不到房间时走原有的重试和“cannot find target room”提示。

临济庄「幽冥桩」的两处笔误(2026-10-15,KB 06 §7.245、§7.250)

linji-zhuang/youming:killer->querry(...)、me->querry(...) 调用的是不存在的函数,永远得 0,恶人比值只按 PK 数算,改为 query;visit_room() 先拿 range 作判断、后从参数里取 range,判断时它还是 0,效果遇到没载入的房间就停,两行取值挪到前面。

银索金铃「点穴」对空手的对手报错(2026-10-15,KB 06 §7.246)

yinsuo-jinling/dian 失手时三种描写之一是“真气贯注”加对手兵器的名字,对手空手时 ob 为 0,三次失手有一次报错。改为 (objectp(ob) ? ob->query("name") : "全身")(泥潭一系原本就写作“真气贯注全身”)。

npc_setup 的浮点截断警告(2026-10-15,KB 04 §6.10)

/clone/obj/npc_setup.lpc、/u/lbc/npc_setup.lpc、/u/yijiu/guanfu/npc_setup.lpc:level = pow(10 * exp, 0.333334); 把浮点数隐式截成整数,编译时警告;改为 to_int(...),结果不变。

存档读不出来:转码损坏(2026-10-15,KB 03 §4.4)

/u/clx/smzhe.o 在转码时坏了:原档里孤立的 GBK 前导字节被 iconv -c 连同后面的引号一起丢掉,restore_object() 报 Illegal mapping format,读档的对象(玩家、留言板、信件、表情)从空白开始。按 raw/ 原档重新解码(孤立字节丢掉、引号保留),驱动核对全部能读回(scripts/lpc_save_encoding.py --from-raw、scripts/lpc_save_restore_check.py)。

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,而不是得到拒绝。3 处(3 个文件)改为 dbase 路径查询 query("family/master_name"):有门派时值相同,没有时得 0;原有的判断照常生效(scripts/lpc_fix_family_index.py)。给映射赋值的写法未动。

用兵器的绝招空手施展时报错(内联写法)(2026-10-15,KB 06 §7.246)

绝招不存兵器变量,直接写 (me->query_temp("weapon"))->query("name")(或在拒绝判断里写 (weapon = me->query_temp("weapon"))->query("skill_type")),之前又没检查兵器:空手 perform 时报 Bad argument 1 to EFUN call_other(),内力等往往已经扣掉。在第一次用到它的那条语句前补上“你必须手持兵器才能使用这一招”的拒绝(scripts/lpc_fix_perform_weapon.py):duanyun-bian/duanyun、feiyu-bian/chanrao、qilin-dao/qilin、riyue-bian/xumi、yunu-jianfa/suxin。

存档读不出来:字符串里的换行、空行(2026-10-15,KB 03 §4.4)

adm/etc/story/npc/bingqipu.o:字符串里有原样的换行(save_object() 存成 \r,restore_object() 逐行读,原样的换行让字符串收不了尾),或有空行(Illegal file format),读档的对象(留言板、NPC、玩家)从空白开始。换行改写成 \r、删去空行(scripts/lpc_save_encoding.py --from-raw),驱动核对全部能读回(scripts/lpc_save_restore_check.py)。

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

用完的物品、要“离开”的 NPC、掷在空中的兵器被移进巫师的工作室(/u/aln/、/u/ryu/),本库没有这些房间。本库的 move() 用 call_other(dest, "???") 载入目的地,找不到文件就报 call_other() couldn't find object,后面的语句都不执行:药(狐狸皮,clone/drug/hulipi)吃下后药效已经生效,随后报错:call_out("destroy", 31) 没有排上,药一直留在身上,药效过去后可以再吃;逍遥三笑散(d/xingxiu/npc/obj/sanxiao)弹出之后报错,毒药留在身上,可以一再使用。2 个文件改为移进本库的虚空房间 VOID_OB(/clone/misc/void),与原作把它们“停放”在巫师房间的用意相同。

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

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

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 载入不了,刷出它的房间也少一个人。本库有 19 个对象设了本库没有文件的武功(16 个名字,最多的是 hengshan-sword、jiuyin-baiguzhao、qiankundanuoyi)。三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。抽样载入这些对象:19 个里 17 个通过,其余 2 个停在别的错误上(d/murong/npc/murong-fu:*No program in object '/d/murong/npc/murong-fu'!;quest/wei/helian:*No program in object '/quest/wei/helian'!)。(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 映射不到,门派弟子用普通轻功/拳脚)。按核对过的对照表改正,共 31 处、25 个文件:dashouyin→dashou-yin(7 处)、lingji-zhuang→linji-zhuang(7 处)、qiankundanuoyi→qiankun-danuoyi(3 处)、sanying-zhua→sanyin-zhua(2 处)、chuosui-zhang→chousui-zhang(2 处)、pudu-chang→pudu-zhang(2 处)、stuff→staff(1 处)、huagu-mainzhang→huagu-mianzhang(1 处)等共 14 个名字。(scripts/lpc_fix_skill_names.py)

出口指向不存在的房间(2026-10-19,KB 06 §7.257)

下列房间的出口写的文件不存在,而对面恰好只有一个房间的出口指回本房间,所以按对面的房间改正(笔误、大小写、路径少了一级):

改后房间照常载入(与 HEAD 逐个对比,没有退步),出口现在指向存在的房间。

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

adm/daemons/rankd.lpc 的 query_respect()、query_rude()、query_self()、query_self_rude() 里 6 个函数开头补上 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 文件。

缺失的 unew()(2026-10-19,KB 06 §7.262)

本库的 NPC 和兵器在 create() 里调用 xkx 一系的模拟函数 unew(file)(new(),但遵守物件的 unique 上限),/adm/single/simul_efun.lpc 里没有,调用者全部报 Undefined function unew(以及接收结果那一行的 Bad assignment ( object vs unknown )),载入不了。补上和 dtsl、pkuxkx、sjpl2 等库一样的 unew()(文件不存在时返回 0,不会让调用者死在 carry_object() 里)。

x == "a" || "b":后面的字符串常量永远为真(2026-10-19,KB 06 §7.281)

if (arg == "yuyou" || "yu you") 里的 "yu you" 是非空字符串,永远为真,整个条件对任何 arg 都成立;作者要的是每个字符串都和变量比较。在每个字符串前补上变量和运算符(scripts/lpc_fix_literal_or.py;forrest2.lpc 是 dir == "north" && "south",手工改成 (dir == "north" || dir == "south") && ...):/d/gaibang/ml.h(1 处)。

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/heimuya/zongtan.lpc(1 处)。另有 /d/hangzhou/huagang1.lpc(1 处) 的条件下面做的是别的事(== 和 || 也可能是本意),保持原样。

比较链的人工修正(2026-10-20,KB 06 §7.282)

d/hangzhou/huagang1.lpc:挖泥的判断 arg != "泥" || arg != "mud" 永远为真,随便挖什么词都挖出湿泥,后面的“你要挖什么?”走不到;本意是参数是“泥”或 mud 时才挖,改成 == 和 ||。

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,共 38 处)。改动:/clone/obj/u/xuanyuan/skill/meinu-quanfa.lpc(1 处)、/clone/obj/u/xuanyuan/skill/qiankun-danuoyi.lpc(7 处)、/d/city/jiuguan2.lpc(2 处)、/d/city/npc/mj.lpc(3 处)、/d/city/npc/mjxiaosi.lpc(3 处)、/d/city/wumiao.lpc(1 处)、/d/city/zuixianlou.lpc(1 处)、/d/heimuya/jiudian.lpc(1 处)、/d/kunlun/luanshiduibak.lpc(2 处)、/d/quanzhou/qzkedian.lpc(1 处)、/d/quanzhou/wglou1.lpc(1 处)、/d/xiakedao/no_pk_room.lpc(1 处) 等 16 个文件。

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

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

翻页器 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。

巫师命令不带参数时对 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 与工作树对照,没有退步。

玄剑录华山台阶 valid_leave():living(obj)(2026-10-21,KB 06 §7.348)

object obj; 从未赋值,living(obj) 抛错,离开这间房一律失败。别的库的同一个房间没有这个函数;判断的是走的人,改成 living(me),去掉 obj。改动:/d/huashan/stair.lpc。

拼错的 call_out() / input_to() 目标(KB 06 §7.349,2026-10-21)

计时器或输入提示给驱动的函数名和文件里定义的差一个字母;驱动对不存在的函数静默不理,所以对应的计时从来没有触发过。调用和配套的 remove_call_out() 改成已定义的拼法:药炉:burning2 / burning3 → 文件里定义的 buring2 / buring3;do_destroy → 文件里定义的 do_destory。改动:/d/huanghe/npc/tc_wang.lpc、/d/xingxiu/npc/obj/yaolu.lpc、/d/xingxiu/obj/yaolu.lpc。

保护任务的商人 clone/obj/job/job_protect_npc.lpc:两处笔误让整个文件从来没编译过(2026-10-21,KB 04 §6.29)

clone/obj/job/protect.h(保护商人的任务)克隆这个 NPC,但文件有两处笔误:int baohu(string age) 的函数体用的是 arg(Undefined variable 'arg'),而 init() 登记的是 do_baohu,函数名也对不上;query->("id") 多了一个 ->。改成 int do_baohu(string arg) 和 query("id")(if (!player = find_player(...)) 报的 Illegal LHS 只是前面语法错误引出来的连锁错误,这种写法本身是对的,没有动);文件第一次能编译后带出 7 个 Unused local variable 警告(sacrifice、exp_rate、skill_rate、lmt、menpai、killer、kill_num),按 KB 04 §6.10 删掉。baohu / protect 两个动词现在有处理函数,任务的商人也第一次能出现。改动:/clone/obj/job/job_protect_npc.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.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 17 条注册(12 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/d/beijing/npc/duolong.lpc(do_jobwith)、/d/beijing/obj/kuanglu.lpc(do_look)、/d/changan/npc/zhangsan.lpc(do_vendor_buy)、/d/kunlun/npc/mingjiao_npc.lpc(do_qingjiao)、/d/murong/mrjob/mrnpc/mr_obj/mr_lingpai.lpc(do_zhuo)、/d/quanzhou/npc/gongping-zi.lpc(do_bihua、do_halt)、/d/xiakedao/obj/car.lpc(do_hire)、/d/xingxiu/npc/feng.lpc(do_ketou)、/d/xingxiu/obj/sanxiao.lpc(throw_ob2)、/inherit/misc/fboard.lpc(do_list) 等共 12 个文件。最多的函数名:do_bihua 3 处、do_qingjiao 2 处、do_halt 2 处、do_hire 2 处、do_jobwith 1 处。没有动(读过):/clone/obj/job/job_protect_npc.lpc 的 do_baohu(对象本身编译不过,登记留着,等对象能加载再处理)。

注册到不存在函数的 add_action,第二轮:宏路径父类和静态解析不了的对象(2026-10-21,KB 06 §7.349)

第一轮的静态解析读不了经宏路径继承父类的对象(#define ROOM STD_DIR "room/room" 这类定义的正文以另一个宏开头)和它解析不了的继承,这些对象当作 UNSURE 没有扫;第二轮修了解析器,重扫并对这些对象在运行时问 function_exists()。add_action("函数", "动词") 绑定的函数,在对象本身、它包含的头文件、它继承的父类和继承它的子类里都没有定义(scripts/lpc_scan_callout_targets.py 静态解析,子类用传递闭包复核,scripts/lpc_verify_handlers.py 运行时 function_exists() 复核)。驱动从玩家最新的一条动作往回找,遇到这样的句子就报 *Function for verb 'drop' not found.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 18 条注册(6 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/d/murong/shuichi.lpc(do_strike)、/d/murong/shuteng.lpc(do_insert)、/d/taohua/mishi.lpc(do_study)、/d/xingxiu/xueling/exit1.lpc(do_ling)、/inherit/room/city.lpc(do_jimou、do_xunlian)、/kungfu/class/gaibang/zhulin3.lpc(do_bake、do_cut、do_kao、do_remove等)。最多的函数名:do_strike 2 处、do_insert 2 处、do_cut 2 处、do_remove 2 处、do_wash 2 处。

基类的 add_action 登记:处理函数只在后代里定义,基类自己被加载时报错(2026-10-21,KB 06 §7.349)

房间、NPC、公告板这类基类,init() 里登记的 add_action("函数", "动词") 的函数只由继承它的后代定义;基类对象自己被直接加载(房间可以走进去、NPC 可以克隆)时,访客输入这个动词,驱动报 *Function for verb 'x' not found.(第一轮扫描把这些当成“有后代定义,是活的”放过了,scripts/lpc_verify_handlers.py 在基类对象上跑 function_exists() 才看出来)。登记前加 if (function_exists("函数", this_object(), 1))(第三个参数 1:默认 function_exists() 把 protected / private 函数当作没有定义),后代照常登记。共 6 处、1 个文件(scripts/lpc_fix_dead_add_action.py --guard):/d/bwdh/kantai.lpc(do_abandon、do_practice、do_study)。

对象路径里的 ..: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")。

(: 函数 :) 指针指向对象自己没有定义的函数(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。 还剩 14 个函数没有补(inquiry 7, chat_msg_combat 6, other 1):原型还在函数体丢了、或者根本没写过,没有可以直接补回的捐赠(别的库同一路径的文件里没有这个函数,或者它还要用本库没有的辅助函数、签名不同),不编造:/cmds/usr/scheme.lpc:playerp、/d/city/npc/hua_girl.lpc:ask_me、/d/emei/npc/guide.lpc:ask_job_begin、/d/emei/npc/guide.lpc:ask_job_finish、/d/huanghe/npc/tc_hu.lpc:ask_job、/d/huanghe/npc/tc_you.lpc:ask_join、/d/kunlun/npc/wushi.lpc:auto_perform、/d/kunlun/wushi.lpc:auto_perform、/d/murong/npc/murong-fu.lpc:ask_baochou、/d/shaolin/npc/xuanci.lpc:ask_tiaoshui 等另外 4 个。

读不到的留言板:"/clone/board/x"->foo(); 在 create() 里报错(2026-10-22,KB 06 §7.357)

xkx 系的房间在 setup() 之后写 "/clone/board/gaibang_r"->foo(); 来加载留言板(foo() 本来就没有定义,只为了触发加载)。这个库没有这个文件,call_other() 报 *call_other() couldn't find object '/clone/board/gaibang_r',对象留在内存里、load_object() 却抛错:玩家第一次走进房间就看到错误,写在 create() 中间的还会切掉后半段。scripts/lpc_fix_missing_foo.py 把 1 条单独成行的语句(/clone/board/pigroom_b;/d/city/duchang2.lpc)注释掉并写明原因(别的房间里同样的语句本来就是作者自己注释掉的)。证明和 45 个库的整体加载检查见 KB 06 §7.357。

不带前导斜杠的物品路径:缺失物品的 new() / carry_object() 调用链加了保护(2026-10-22,KB 06 §7.73 补充)

carry_object("d/city/obj/jiasha")->wear(); 这类路径不带前导斜杠的写法在这个驱动里按根目录解析(new("d/city/obj/changjian") 能加载 /d/city/obj/changjian),所以和带斜杠的一样:文件不在压缩包里时 new() 返回 0,->wear() 在 0 上报 Bad argument 1 to EFUN call_other(),整个 NPC 加载不了。此前的保护工具把这种写法当作「相对当前目录,没法判断」跳过了。2 个文件里 2 处(clone/cloth/cloth)改成带保护的块(scripts/lpc_guard_carry_object.py)。 对这 2 个改动文件做加载检查(HEAD 与工作区对比):HEAD 能加载 0 个,现在 2 个(新增可加载 2 个,回归 0 个)。

log/ 下的日志子目录没有人建:写日志的 write_file() / log_file() 报错(2026-10-22,KB 06 §7.358)

这个库的代码往 log/ 的 2 个子目录写日志:log/beijing(d/beijing/helper.lpc)、log/job(adm/etc/task/feizei/lingzhi.lpc)。压缩包、git clone 和站点包里都没有这些目录(本地启动没有写过它们,gen_keep_dirs.py 没记),write_file() 对不存在的目录抛 *Wrong permissions for opening file /log/dlog/money for append. "No such file or directory",调用它的函数在写日志那一行中断,后面的奖励、标志、步骤都没有执行。本库的 log_file() 是 write_file(LOG_DIR + file, text),目录不存在就抛同样的错误(实测)。把 log/beijing、log/job 记进 scripts/wasm_keep_dirs.txt(scripts/lpc_keep_log_dirs.py;站点和 pristine 树按它建空目录)。证明(scripts/lpcc_eval.py,在 /adm/daemons 这类目录下的求值对象里写 /log/zzdirprobe/...,新进程)见 KB 06 §7.358 的实测表。

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

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

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

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

本库 118 处(51 个文件):拼名字的赋值 91 处、与汉字常量比较 25 处、改名官的长度检查 1 处、query("name")[..] 1 处。例:name[0..01] → name[0..0](/clone/npc/qiu.lpc:299);name[4..5] → name[2..2](/clone/npc/qiu.lpc:299);name[0..3] → name[0..1](/d/city/npc/shiye.lpc:129)。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,不要重跑。

长度检查按字节写,旁边的提示按汉字写:2 处(2026-10-22,KB 06 §7.362)

老档的 strlen() 数字节(一个汉字 2 字节),转成 UTF-8 后它数字符:提示写「必须是 2 到 6 个中文字」的检查还是 strlen(name) < 4 || strlen(name) > 12,于是两三个字的房名、帮会名、群名、聊天室名、称号被提示自己说允许的那句话拒绝,上限却是提示的两倍;留言板的标题检查 i < 4 || i > 24 || i % 2 还会拒绝所有奇数个字的标题(post 你好吗)。本库 2 处(1 个文件):if ((strlen(word) < 2) || (strlen(word) > 8) || i % 2) {(/clone/beast/yufeng.lpc:81)。scripts/lpc_fix_strlen_gate.py 以提示为准:条件里每个 strlen 门槛都恰是提示所说数字的两倍才折半,存长度的变量(i = strlen(arg);)的门槛和 || i % 2 奇偶项、其后循环的 i % 2 == 0 && 一并处理;提示里有英文 / 字母 / 字节的不动。证明(scripts/lpcc_eval.py,bxsj 的留言板,求值对象继承 NPC 并重写 edit() 记录调用,command("post 标题")):HEAD 拒绝「你好」和「你好吗」、放行 23 个字的标题;改后接受前两个、拒绝第三个;shenmo 的 named.lpc invalid_new_name("张三"):HEAD 回 名字只能是2-4个中文字。,改后回 0。抽样 24 + 15 个库的 HEAD / 工作树加载对比 0 退步。

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

段延庆 yanqing 空格污染无法加载(2026-10-27,KB 04 §6.25 Also)

kungfu/class/dali/yanqing.lpc:汉字与 dbase 键都被插空格(" id "、" family / master_id "、空格版 say),另有 query("PKS) 缺引号和嵌套 void init。无法就地修补;用同系已修好的 xkm 副本覆盖(bmxkx2001 / xkx2001 / xuanjianlu 三份原相同)。证明:HEAD 加载失败;改后加载成功。

深度功能测试(2026-10-27,§6.25 tell_object 缺 me(自动放弃/愿否))

一参 tell_object("…") 在已有 me 时改为 tell_object(me, "…")(city.lpc)。证明:抽样 city / xiaolongnv 加载。

深度功能测试(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… 行。