Divine Land (shenzhou)

✅ 可玩

神州

shenzhou

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

▶ 开始游玩 · Play Now

自我介绍为【神州】("Divine Land"),站点由"神州开发公会"运营,属于"ES II"引擎家族(与本项目中的随缘洗剑录、天下无雪等同宗),是一个经过多代维护者接力开发的老牌中文武侠 MUD。新角色从"新手的殿堂"开始,逐步熟悉江湖世界后正式踏入游戏,围绕属性天赋、门派拜师、武学修炼的经典武侠养成核心之外,还建有离线练功、帮派系统与铁匠职业等长期养成玩法。

English

A veteran Chinese wuxia mudlib self-styled 'Divine Land,' run by the Shenzhou Development Guild and built on the ES II engine family shared with sibling games Suiyuan Sword Legend and Heaven Without Snow in this collection, maintained across several generations of developers. New characters start out at the 'Hall of Novices' to get their bearings before formally entering the jianghu, with long-term systems for offline training, guilds, and a dedicated blacksmith profession rounding out a classic stats/sects/martial-cultivation core.

README

内容亮点

在线试玩

https://mudlibs.fluffos.info/shenzhou/

管理员账号 / Admin account

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

本地运行

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

游戏端口:40066。

NOTES · 移植与修复记录

神州 (shenzhou) — archive #72

What this is

archives/神州.rar, root nested two levels deep at raw/sznew/sznew/ (the outer sznew/ directory is just the archive's top-level wrapper, the real mudlib root is sznew/sznew/). bin/config.sz's own name : field decodes (GB18030) to 【 神州 】 — the game's own self-identified name matches the archive title exactly ("神州" = "Divine Land/Land of the Gods"), no slug divergence to note. The bundled readme.txt (author unclear, dated Nov 2002) describes it as "a very messy mudlib... according to Hxsd it's ShenZhou" and complains about wizards leaving junk under /u — an accurate warning: this archive has an unusually large number of personal wizard sandbox directories (u/<name>/) with duplicate/backup copies of core files, dead experiments, and stray content, on top of the live game tree.

Lineage: adm/single/master.c's header comment traces the full "ES II" engine chain explicitly — // for ES II mudlib, // original from Lil, // rewritten by Annihilator (11/07/94), // modified by Xiang for XKX (12/15/95), // modified by Xuy for XKX (08/21/97) — the same lineage already seen on es1_win/esI/xkx2001/rzrmud/xo/bmxkx2001/ kxkj/yueyingqiyuan/wuhanzhan/haiyang2/yanhuangwuhun/ yhyxs/xuanjianlu. Layout is adm/single/{master,simul_efun} + adm/daemons/* + adm/simul_efun/* (fragments #included into simul_efun.c), matching that whole family. A further ShenZhou-specific layer was added on top by a "Karlopex@sz" (site admin, credited in several comments dated 06/03/2002) and "sdong"/"buwu" (earlier maintainers, dated 1998) — this looks like a multi-generation fork rather than a fresh ES II checkout.

Size: 14,497 raw files, 12,740 .lpc/.c files after conversion (86MB) — a normal-sized lib for this project, not a mega-lib; the full lpcc_check.sh sweep ran twice without any memory pressure (peaked at ~1.6GB RSS on the lpcc process, host had 8-19GB free throughout both runs).

Fixes applied (and why)

Standard/catalog fixes, applied proactively before first boot

1. AGENTS.md §15h (is_chinese/check_legal_name GBK byte-range bug — nearly universal): adm/simul_efun/chinese.lpc's is_chinese(str) was strlen(str)>=2 && str[i] >= 161 && str[i] != 255 && (i%2==0 implies str[i] in 176..247) (a GBK lead/trail-byte range check, always false against UTF-8 codepoints) → replaced with strlen(str)>=1 && str[i] >= 0x4e00 && str[i] <= 0x9fff (CJK Unified Ideographs codepoint range, checked per character, no byte-parity gating). adm/daemons/ logind.lpc's check_legal_name(name, ob): bound (strlen(name) < 2) || (strlen(name) > 8) || i%2 (byte-count bound + meaningless parity check) → (strlen(name) < 1) || (strlen(name) > 4) — the message text already said "必须是 1 到 4 个中文字" (1 to 4 Chinese characters), so halving 2/8→1/4 matches what the message always promised, not a guess. Its sliding-window loop if( j%2==0 && !is_chinese(name[j..j+1]) ) { name[j]+=128; name[j+1]+=128; } (a GBK/BIG5 byte-shift "auto-correct" hack with no valid meaning against Unicode codepoints, per §15h item 4) → if( !is_chinese(name[j..j]) ) { <reject>; }, using the reject message text the original author had already written but left commented out. Verified via 2 independent full registration tests (below) that real Chinese names ("秦风", "林风") are accepted.

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 (along with a dead /adm/daemons/network/https entry pointing at a file that doesn't exist anywhere in the archive — harmless either way, but cleaned up for tidiness). Also checked §15ab's "inline DNS call outside preload" variant: cmds/usr/mudlist.c's main() does call DNS_MASTER-> query_muds(), but it's guarded by its own find_object(DNS_MASTER) check first and degrades to a harmless notify_fail() when not loaded — confirmed safe by inspection (this command is never invoked during boot or the registration flow itself) and by zero related 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), 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/master.lpc (this lib has several: chinese.lpc, message.lpc, object.lpc, etc, all #included into adm/single/simul_efun.lpc) is still recognized as legitimate.

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 using a local (same-directory) #include. Pure insurance; not needed to unblock the observed boot/registration path itself (see "confirmed not needed" below), but free and matches the established per-lib checklist.

5. AGENTS.md §15ae (private nomask command-hook, the newest catalog entry, standing policy for this session) — checked proactively and found present: feature/command.lpc declares private nomask int command_hook(string arg), inherited into the player body and registered via add_action("command_hook", "", 1) in enable_player(). Exactly the pattern that broke every post-login command with zero visible error on xuanjianlu (#70) and retroactively on bmxkx2001 (#45). Fixed by dropping private (kept nomask) BEFORE the first boot attempt, then verified with a real post-login look/score/i/quit sequence (see transcript below) — this is exactly the check the task's standing policy requires, and it would have silently broken this lib's entire gameplay loop if skipped.

6. AGENTS.md §8e (tail is not a real FluffOS efun) — fatal here: adm/simul_efun/message.lpc's tail(string file) called efun::tail(file); since this file is #included directly into adm/single/simul_efun.lpc, the compile error (Unknown efun: tail) would take down the entire simul_efun object and the boot with it. Reimplemented in plain LPC (read file, split on \n, take the last 10 lines, write them). A second, non-fatal instance survives in cmds/wiz/tail.lpc/cmds/imm/checkgx.lpc, but those call the bare (now-fixed) simul_efun tail(), not efun::tail() directly, so no further change was needed there. A THIRD instance also exists at u/karlopex/backup/adm/simul_efun/message.lpc — confirmed this is a stale personal backup copy of the pre-fix file under a wizard's own u/karlopex/backup/ sandbox, never loaded by anything else (not the live /adm/simul_efun/message the real simul_efun.lpc #includes) — left as-is, it only shows up as lpcc-sweep noise.

7. AGENTS.md §15s (message() 4th-arg int-0 rejection): adm/ simul_efun/message.lpc's tell_room(ob, str, exclude) passed a bare exclude (defaults to int 0 when called in the common 2-arg form) straight through as message()'s 4th argument, which this driver's message() efun (typed void | object | object *) rejects when given explicitly. Fixed with exclude || ({}).

New finding this pass — NOT yet in AGENTS.md's catalog

8. A log_error() compile-warning handler unconditionally calling wizardp(this_player(1)), which lazily load_object()s the security daemon mid-compile — a fatal boot crash on the very first preloaded daemon, on every fresh boot, before this fix. This is a new, specific manifestation of the §4/§15w family worth its own catalog note.

master.lpc's log_error(string file, string message): ``lpc if(wizardp(this_player(1))&&(strsrch(message,"Warning:")==-1)) efun::write("编译时段错误:" + message+"\n"); ` Two compounding bugs: - (a) wizardp(this_player(1)) was called unconditionally, even when this_player(1) is 0 (there is no player at all during preload). wizardp() (adm/simul_efun/wizard.lpc) does SECURITY_D->get_wiz_level(ob) — an ordinary call_other, which lazily load_object()s securityd the first time anything invokes it. Since this driver funnels compile warnings through log_error() too (§15w — confirmed here: /feature/treemap.lpc:2: warning: Unknown #pragma, ignored is what triggered it), and the very FIRST preloaded daemon (storyd, which pulls in feature/ dbase → feature/treemap) fires this warning before securityd (later in adm/etc/preload) has ever loaded, SECURITY_D-> get_wiz_level()'s lazy load attempt threw *Object cannot be loaded during compilation. — on EVERY boot, unconditionally, before any daemon finished preloading. Because this error itself then escalated to master.lpc's error_handler(), which ALSO does an unguarded new(CHANNEL_D) (another mid-compile object instantiation, since channeld hadn't loaded yet either at this point in the preload order), the driver printed a further Error in mudlib error handler: cascade. This did not infinite-loop (unlike §4's valid_read/valid_write shape) since error_handler() isn't re-entered a second time for its own secondary failure, so the boot did NOT hang or crash outright — but it produced boot-time noise and, more importantly, would recur (with the same crash-and-cascade shape) for literally any compile warning anywhere, at any time post-boot too, since SECURITY_D/CHANNEL_D being loaded by the time of a LATER warning is not guaranteed either. - (b) strsrch(message,"Warning:") (capital W) never matches this driver's actual lowercase warning:` prefix, so the intended warning-suppression (skip broadcasting warnings, only broadcast real errors) never fired at all — every single compile warning would have been broadcast to any connected wizard as if it were a serious runtime bug, compounding §15w's usual noise problem.

Fix: guard wizardp(this_player(1)) behind this_player(1) && (skip the whole wizard-hood check — and so the lazy securityd load — when there's no player at all, which covers every preload-time warning), and fixed the case-sensitive "Warning:" check to strsrch(lower_case(message), "warning:"). Also hardened master.lpc's error_handler() itself as cheap insurance (matching §4/§15w's spirit): wrapped its new(CHANNEL_D)/do_channel()/destruct() sequence in a catch { } block, and added a permanent efun::write_file("/log/ RUNTIME_ERRORS", ...) write before that, so a real runtime error is never silently lost even if CHANNEL_D happens to be unavailable when it fires. Found and fixed BEFORE the first real driver boot (via lpcc compiling /adm/single/simul_efun in isolation first, per the pipeline's step 5) — confirmed by re-running the same lpcc compile after the fix: the crash cascade is gone, storyd's warning is now silently absorbed as intended, and the real driver boot afterward produced a completely clean log/debug.log (zero cannot/denied/ crash/fatal hits) across the whole session, including two full registration flows.

9. AGENTS.md §15n (custom securityd.lpc ACL blocking the driver's own mid-connection compiles) — checked proactively and found present: adm/daemons/securityd.lpc's valid_read() is a genuine custom ACL (exclude_read/trusted_read mappings keyed by directory and wizard-status, not just a find_object()-only stub), and only allowed func == "file_size"/"stat" early — missing the load_object/recompile_object/include allowance. Added proactively before the first boot, since a fresh unauthenticated connection's default (player) status would otherwise deny the FIRST never-preloaded /adm or /cmds object the registration flow happens to lazily touch. (Its valid_write counterpart already had a suf­ficiently permissive shape and needed no change.)

10. §3 counterexample, wide blast radius (52 files): convert_lib.sh's blanket \bstatic\b→nosave sed rewrote every log_file("static/ XXX", ...)/"/static/XXX" path-literal reference (this lib logs extensively to a real log/static/ seed directory — crash logs, read/write-denial audit logs, promotion logs, editor-session logs, etc.) to "nosave/XXX", silently orphaning that real directory. Found via grep -rlas '"nosave/\|"/nosave/' (52 files hit — master. lpc, securityd.lpc, message.lpc, and 49 further cmds//d// kungfu//u/-tree files) and reverted with a targeted sed -i 's/nosave\//static\//g' scoped to just those 52 files (safe: the bare keyword nosave/static never appears immediately followed by a / anywhere else in this lib, confirmed by re-checking for zero remaining "nosave/ hits after the revert). Also confirmed the physical log/static/ seed directory (containing CRASHES.lpc, LASTCRASH, CALL_PLAYER.lpc, etc — already renamed .c→.lpc by the same blanket rename, consistent with what log_file()'s own + ".lpc" suffix now expects) is intact and untouched.

Content-level fixes found via the lpcc_check.sh sweep (genuine bugs, not sweep artifacts)

11. is_killing(who) type mismatch (object vs the declared string id) — the same class of copy-paste bug already catalogued (§15b's nitan_ceshi/yhyxs findings): feature/attack.lpc declares varargs int is_killing(string id) and every correct call site does is_killing(ob->query("id")), but 5 files (all clearly copy-pasted from one template, sharing the exact same line/dialogue text) call is_killing(who) with a raw object instead: clone/npc/guidao.lpc, clone/npc/guidaobak.lpc, d/city/npc/guidao.lpc, d/shaolin/npc/chengkun.lpc, d/shaolin/shaolin/npc/chengkun.lpc. Since this driver's static type checker enforces the declared parameter type strictly on a *direct* (non-->) call, all 5 failed to compile entirely. Fixed by changing each to is_killing(who->query("id")). (These are individual NPC files, not the player-body class — unlike tianxia's/nitan_ceshi's finding, this did NOT block character creation; found purely via the lpcc sweep, not via any interactive-test crash.)

12. A genuinely corrupted stale save file crashing create() (§15m shape): clone/obj/genmap.lpc/clone/obj/mapdb.lpc (an auto-generated NPC-map cache feature, share one save file, /data/npc/map) both threw *restore_object(): Illegal mapping format while restoring map. — confirmed this was NOT reachable during the real boot + 2 full registration/post-login sessions (zero hits for "genmap"/"mapdb"/"Illegal mapping" in log/debug.log) — this feature is never preloaded and never touched by ordinary registration/play, so it's an lpcc-sweep-only finding, not a live boot defect. Fixed anyway (cheap, matches precedent): moved the stale data/npc/map.o aside to map.o.stale-corrupted-orig so restore() finds nothing (not an error) and create()'s own fallback (if (!restore()) save();) produces a fresh file instead. Re-verified clean via lpcc afterward.

13. Two dead pure-ASCII-art zone maps mistakenly caught by the blanket .c→.lpc rename (§12 pattern): d/mingjiao/map_mingjiao.c (a "明教地图"/Ming-Cult-zone box-drawing map, zero LPC code) and d/hangzhou/hangzhou.c (a "杭州"/Hangzhou-zone map, likewise zero code) — confirmed via grep -rl that nothing else in the whole tree references either by name. Renamed to .txt/.txt.mapart respectively so the sweep's pass/fail signal stays meaningful for files that actually matter.

14. Pre-existing single-byte GBK corruption + missing closing quote, confirmed present in the RAW archive itself (not introduced by our conversion) — found in d/shenlong/sea.lpc/sea2.lpc: a message("vision", "..."); call had a garbled mid-string segment ("不傻米呓豢矗词") ending in an unclosed string literal before a +. Root-caused by direct raw-byte inspection of the pre-conversion archive (d/shenlong/sea.c): the raw GBK bytes contain 3 genuine Private-Use-Area-mapped characters (U+E3DD, U+E0EF, U+E139 once decoded under gb18030, matching the xo_final/"mohe-zhi.lpc" precedent) immediately followed by an isolated, non-GBK-pairable byte 0xc7 sitting directly before the closing " — this exact byte is ALREADY malformed in the raw archive (confirmed: decoding the raw bytes with Python's gb18030 codec fails at that exact byte, independent of any tool of ours), and convert_lib.sh's iconv -c lossy-recovery pass ate that byte's would-be-partner AND the adjacent closing quote together (the exact iconv -c adjacent-real-byte-loss pitfall documented in AGENTS.md's Encoding section). A THIRD, uncorrupted sibling copy of this exact same message (d/shenlong/jushi.lpc, appearing twice) confirmed the original, undamaged phrasing — "你发现一个浑身水淋淋的家伙被海水冲 上岸来,不由得走近一看,原来是" — which was used to restore sea.lpc/sea2.lpc to match exactly (not a guess: cross-confirmed against live, working sibling content in the same zone, same author). All 3 files ALSO used an old-MudOS-era octal byte escape ("\241\243", meaning "raw GBK bytes 0xA1 0xA3" under the ORIGINAL byte-oriented driver — which happen to form "。", the full-width Chinese period) to represent one character instead of embedding it directly; under this driver's UTF-8-native strings, decoding those 2 raw byte-escape values as if they were 2 separate codepoints does not form valid UTF-8, producing error: Invalid UTF8 codepoint in string literal. Fixed by replacing the \241\243 escape with the literal 。 character in all 3 files (sea.lpc, sea2.lpc, jushi.lpc). New pattern worth flagging for AGENTS.md: an old-MudOS-era octal escape sequence (\NNN\NNN) inside a string literal, meant to represent raw GBK/BIG5 bytes for one Chinese character under a byte-oriented driver, is invalid UTF-8 once evaluated by this UTF-8-native driver and must be replaced with the literal character it was meant to produce — grep for \\[0-7]{3}\\[0-7]{3} inside string literals as a proactive check on any new lib (only 3 files affected here, all in the same d/shenlong/ zone, presumably authored/edited together).

15. A single missing-shared-header cascade (§8g variant, 7 files at once): d/zhongnan/npc/move_file/{ma,liu,zhao,tan,hao,wang,sun}.lpc all did #include "auto_perform.h" (quoted, relative) but the real header lives one directory UP, at d/zhongnan/npc/auto_perform.h — quoted includes search the including file's own directory first, then the configured include path, but never an ancestor directory, so all 7 failed identically with Cannot #include auto_perform.h. Fixed by copying the existing (generic, author-credited) header verbatim into d/zhongnan/npc/move_file/auto_perform.h — not fabricated content, just duplicating a real header into the one additional location quoted-include resolution actually needs. Resolved all 7 failures in one shot.

16. Stale absolute-path prefix leftover from the original server's own mount point (2 files): d/kunlun/obj/guzheng.lpc did #include "/shenzhou/d/kunlun/obj/flute.h" and u/comfort/comfort_work.lpc had a data-mapping value "yz" : "/shenzhou/d/city/kedian.lpc" — both referencing a /shenzhou/ absolute-path prefix that doesn't exist in our mudlib root at all (our mudlib root doesn't nest a shenzhou/ subdirectory; this must be a leftover from however the original site's own filesystem was laid out, e.g. /shenzhou/ was literally their mudlib's real absolute mount point on the original server, baked into a couple of hardcoded paths by mistake). Both real target files (flute.h, kedian.lpc) exist at the equivalent path with the /shenzhou prefix simply dropped — fixed both occurrences (confirmed via grep -rlas '/shenzhou/' that these were the ONLY 2 occurrences anywhere in the whole lib).

17. Pre-existing single-byte GBK corruption merging a string literal across a line break (a distinct instance from #14, same general shape): d/changbai/damk.lpc had "...地对洞\n "了 出去。\n" — confirmed via raw-byte inspection of the pre-conversion archive that the ORIGINAL archive already has a malformed (non-decodable even under gb18030) byte sitting between "洞" and the string's closing quote, predating our conversion entirely (the raw file already shows a U+FFFD-shaped replacement artifact at that exact position when decoded permissively). Fixed by merging the two line fragments into one properly-closed string literal on a single line, dropping just the one corrupted byte, preserving every other character exactly.

What I confirmed was NOT needed (and how)

Registration flow + post-login command verification

Read the actual adm/daemons/logind.lpc get_id/confirm_id/ get_name/new_password/confirm_password/check_password/ confirm_check_password/select_gift/get_gift/get_email/ get_gender callback chain before scripting the test. Full flow for a brand-new character: BIG5 y/n → English id (3-8 lowercase letters, new-keyword-free — any unused id goes straight to character creation, confirm_id y/n) → Chinese name → use-password (min 5 chars, entered twice) → a SEPARATE 保密密码/"security/recovery password" (min 10 chars, entered twice — used for password-recovery, not the login password itself) → a gift/attribute-point selection (0-4, 0 = fully random) → accept-the-rolled-gift (y/n) → email address (id@domain format required) → gender (m/f) → enter_world().

Driver boot: cd libs/shenzhou && setsid nohup ~/src/fluffos/build-debug/src/driver config.fluffos & disown (via the Bash tool's own run_in_background) → Accepting telnet connections on 0.0.0.0:40066. / Initializations complete. with zero errors of any kind in the preload log (after the log_error/wizardp fix — before it, every single boot crashed/spammed on the very first preloaded daemon, storyd).

Registration test 1 (mudclient.py, one continuous connection):

--send "n" --send "qinfeng" --send "y" --send "秦风"
--send "mypassword1" --send "mypassword1"
--send "checkpassword1234" --send "checkpassword1234"
--send "0" --send "y" --send "[email protected]" --send "m"
--send "look" --send "score" --send "quit"

Result: BIG5 prompt → GB accepted; id "qinfeng" accepted; character creation confirmed; Chinese name "秦风" accepted (proves the §15h fix works — this is the single most important check per the standing policy); use-password/security-password both set; gift rolled (膂力24/悟性16/根骨20/身法18/剩余22), accepted; email accepted; gender male → entered the actual game world ("新手的殿堂"/Hall of Newbies, full room description + exits down/enter/xkd). look re-displayed the room correctly. score displayed the full character status sheet (身份卡: age, gender, birth-time, sect, attributes, HP/qi/food/water/ potential bars, kill/death counts, faction/property/residence/job/level/ income fields) — this is the definitive proof the command_hook fix (§15ae) actually works, since score is dispatched through the exact add_action-bound command_hook this task's standing policy warns about. quit produced a clean "你丢下一件布衣...欢迎下次再来!" farewell. Zero errors of any kind in log/debug.log across this entire session.

Registration test 2 (independent, same continuous-connection discipline, different name/gender to also check dup-name handling isn't accidentally triggered and a second gender path works):

--send "n" --send "linfeng" --send "y" --send "林风"
--send "mypassword1" --send "mypassword1"
--send "checkpassword1234" --send "checkpassword1234"
--send "0" --send "y" --send "[email protected]" --send "f"
--send "i" --send "quit"

Result: identical flow, female gender accepted, entered the same starting room ("新手的殿堂"). i (inventory) command — another command_hook-dispatched command, distinct from look/score — listed real starting items (布衣/Cloth, 乾坤地图/Newbie maps, 林风的锦囊/a name-personalized pouch, confirming the character's own name correctly threads through generated item descriptions). quit again clean. Zero errors of any kind in log/debug.log across this second session too (cumulative debug.log after both sessions: 598 lines, no error/ denied/cannot/bad argument/undefined hits at all).

Driver killed by exact PID (kill <pid>) after testing completed — not a pattern-match kill (other agents had concurrent driver processes running on other ports at the time; confirmed by cwd/listening-port before touching anything).

lpcc_check.sh sweep

Ran twice (once before the content-level fixes in items 11-17 above, once after, to get an accurate final number). Final: 12,481 / 12,742 pass (97.95%), up from 12,459/12,740 (97.79%) before the second pass's fixes — net +22 passes from ~20 files' worth of fixes (a few fixes resolved multiple files at once: the auto_perform.h header copy alone resolved 7).

Remaining 261 failures, triaged by category (spot-checked a representative sample of each, cross-referenced against the real boot's completely clean debug.log to confirm none of these are live bugs):

Orphaned/known content gaps (not fixed, documented per §13)

Config

config.fluffos: port 40066, mudlib directory → libs/shenzhou/ work (absolute), master file → /adm/single/master, simulated efun file → /adm/single/simul_efun, log directory → /log (resolved relative to the driver's CWD per §6 — libs/shenzhou/log/, driver always launched via cd libs/shenzhou && ... driver config.fluffos). Converted from the original bin/config.sz (GB18030→UTF-8 done FIRST per §5, before any other edit) — original name/default fail message/default error message fields carried over unchanged (【 神州 】 / 什么? / 你发现事情不大对了,但是又说不上来。). A second nearly-identical original config, bin/config.sz2, existed with slightly larger resource limits (external_port_1 style port directive instead of port number, larger array/mapping/string-length caps) — config.sz was used as the base since it uses the plain port number directive this driver reads directly; the resource-limit deltas between the two originals are minor and not consequential for this environment.

Rebuilt-driver / formatter / WASM re-verification pass (2026-07-23)

1. LPC formatter applied across all 12,742 .lpc files in work/: {"total":12742,"written":12597,"wouldChange":0,"unchanged":89, "errors":56}. Checked for the copy(::fn())-adjacent formatter bug found elsewhere this pass (see tianxia/NOTES.md for the full writeup) — zero hits of the broken (: : signature anywhere in this lib's reformatted tree. feature/command.lpc's command_hook is still plain nomask. 2. Native re-test against the rebuilt build-debug/src/driver: booted clean (zero fatal errors). Full registration verified end-to-end via mudclient.py (this lib asks a BIG5-font Y/N question before the ID prompt, has a separate ≥10-character "保密密码" recovery-password step in addition to the normal login password, and caps Chinese names at 4 characters — all discovered by iterating the flow live rather than assumed): id szfmte → confirm → real Chinese name 秦风戊 → login password ×2 → recovery password ×2 → attribute roll (0/random) → accept → email [email protected] → gender m → entered the game world at 新手的殿堂, look/score/i/quit all producing correct real output (character sheet stats matched the just-rolled attributes). debug.log: zero error in error handler/denied/undefined function/bad argument lines. No new fixes needed. 3. WASM test: boots cleanly with zero Undefined function/ denied/Bad argument lines anywhere (this lib apparently doesn't preload any socket/db-dependent daemon that would hit the no-sockets-package wasm restriction). Full registration completed successfully under wasm, same flow as the native test above, through to look/quit producing correct real output at 新手的殿堂. This lib has no IP-format-dependent login gate, so — like sjpl2 — it is fully playable under wasm, one of the best wasm results in this batch.

WASM-enablement pass (2026-07-24)

Standard four-change pass (AGENTS.md §1.3b/§1.3e/§1.5):

1. Loopback-allow (empty/non-string/127.* IP treated as loopback): - adm/daemons/logind.lpc logon() — BAN_D->is_banned() gate and the iplimit > 20 same-IP online cap skipped for loopback. - adm/daemons/logind.lpc get_id() — REGBAN_D->is_banned() registration-ban gate skipped for loopback. - adm/daemons/securityd.lpc valid_wiz_login() — wizard logins from loopback always allowed (otherwise a wizard id absent from wiz_sites is refused outright: "请从登记的地址使用巫师帐号"). - adm/daemons/band.lpc + adm/daemons/regband.lpc is_banned() — loopback/localhost/malformed sites never banned. 2. Uptime gate: none present. Other throttles: none beyond the above. 3. Admin seeded: fluffos / Mud@2026 / 浮浮 → (admin) via data/securityd.o (wiz_status + wiz_sites ".*" + wiz_renwus; CRLF file, edited binary-safe). Registration also required the lib's separate >=10-char recovery password: Recovery@2026. Save files: work/data/login/f/fluffos.o, work/data/user/f/fluffos.o (data/ not gitignored). Verified: update /cmds/usr/score → 成功 with the system-wide update announcement. 4. Retest: fresh registration (szqfa/秦风, deleted after test) into 新手的殿堂 with look/score/quit OK; debug.log completely free of runtime errors.

Retrofit: fail-closed loopback check (2026-07-24)

The loopback-allow gates above were originally written per the (now superseded) defensive instruction to also treat an empty/non-string/ malformed query_ip_number() result as loopback, since older WASM driver builds returned garbage. That driver bug is now fixed upstream (query_ip_number()/resolve() return real values under WASM too), so the "malformed IP = trust it" fallback was a fail-open bypass with no remaining justification. Tightened every gate listed above to the strict pattern: loopback is ONLY ip == "127.0.0.1", ip == "::1", or a leading "127." prefix — a non-string/empty/malformed IP is now treated as untrusted/remote and subject to the gate normally, not silently allowed through. Retested: fluffos login (127.0.0.1, real value under the current driver) still passes every gate; debug.log stayed clean of denied/undefined function/error in error handler.

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

First real *playthrough* pass on this lib (every prior pass verified only registration + look/score/quit + admin login, never real exploration/ combat/sect/net-dead). Read doc/help/newbie in full first — it names the down/enter choice out of 新手的殿堂, the chat channels, fight (safe sparring — "双方都不会打死对方") vs kill, bai/xue/cha for sect-join and skill learning, and the wimpy/death mechanics, matching §10.7's prediction that it's the fastest way to learn the intended path. Confirmed the two bugs AGENTS.md §7.10 already documents for this exact lib (log_error()'s broken-case "Warning:" gate and its unconditional wizardp(this_player(1)) lazy-securityd-load crash, both in adm/single/master.lpc's log_error()) are still correctly fixed from the earlier pass — read the current code directly (see excerpt below), did not re-diagnose as new.

Test character (kept, representative playthrough evidence): id qinfengw, Chinese name 秦风武 (male), password Wuxia2026, security/ recovery password Recovery2026zz. State: rolled gift attributes (膂力 21/悟性20/根骨21/身法22), explored from 新手的殿堂 → 客店 (down) → 北集市 → 东大街 → 中央广场, sparred 流氓头 via fight (safe — the match self-terminated once qi dropped below the documented ~50% threshold, no death, +1 exp), attempted bai/xue against 流氓头 (correctly rejected — he belongs to no sect), attempted a shop purchase at the 鲜花店 flower shop (correctly rejected — new character starts with 0 cash), survived a clean quit → real ~2-minute wait → relogin with location/stats/exp intact, and survived a genuine unclean (net-dead) disconnect → prompt relogin with location/stats intact. Saves: work/data/user/q/qinfengw.o, work/data/login/q/qinfengw.o. Two throwaway characters used to reproduce/diagnose Bug 1 below were also left in place as evidence rather than risk clobbering the repro: hemuzhi/何目之 (the very first character that hit the crash, still sitting in a stale pre-fix netdead state from that session — harmless, a future login just re-authenticates normally) and cezhice/策之测 (used to confirm the SECOND symptom — a permanently-broken board object crashing every subsequent look for the rest of that boot, before the fix). All three passwords Wuxia2026.

Bug 1 (matches AGENTS.md §7.25's "closely related shape", not a new class): an unguarded companion-board force-load in d/city/kedian.lpc's create() let one raw-GBK save file crash the city's central hub room's first-ever visit each boot — and PERMANENTLY corrupted the board object for the rest of that boot, breaking look for every player in that room, not just the first visitor

Files: work/feature/save.lpc's restore() (the fix); root-caused data at work/data/board/kedian_b.o; triggering unguarded call site at d/city/kedian.lpc:71 ("/clone/board/kedian_b"->foo();), one of 71 structurally-identical "/clone/board/<id>"->foo()/->"???"() force-load call sites across the lib's rooms.

``lpc // BEFORE: int restore() { string file; if (stringp(file = this_object()->query_save_file())) return restore_object(file); return 0; } // AFTER: int restore() { string file; int ret; if (stringp(file = this_object()->query_save_file())) { catch(ret = restore_object(file)); return ret; } return 0; } ` This makes ANY future corrupted save file (board, or anything else that inherits this same feature/save.lpc helper) degrade to "restore failed, return 0" — the same as a genuinely-missing file already does, and the same fallback several callers (e.g. the already-documented clone/obj/genmap.lpc if (!restore()) save();` pattern) already rely on — instead of crashing the whole calling chain and potentially corrupting the object's own already-set state. It does NOT recover data from a bad file (that needs the data-level fix above); it only stops a bad file from taking the room/board down with it.

Confirmed working, no fix needed

Corroborating evidence for AGENTS.md §10.8 (driver-level, NOT mudlib-fixable) — a THIRD occurrence of the fatal refcount/double-free crash class, this time with a full C++ backtrace captured

Per the task's explicit instruction to attempt a real, full-duration net-dead wait: net-deaded qinfengw for real (closed the socket, no quit) and blocked in the foreground for the genuine 900-second (NET_DEAD_TIMEOUT, include/user.h:11) window, checking the driver's own PID/RSS every ~50s (stayed stable, 109→116MB, no runaway growth — ruled out AGENTS.md §10.8's own "unbounded RSS growth" concern separately). At roughly the 10–11 minute mark (before the 15-minute net-dead timer itself ever fired — confirmed via save-file mtimes that this player's own user_dump() never ran), the driver process aborted outright — ss/ps confirmed the PID and the port both gone. This is NOT the mudlib bug above; it is the SAME driver-level class already documented in AGENTS.md §10.8 (xjcq2000, shiji) — this is the third independent lib to hit it, and the first time (of these three) the exact C-level signature was captured with a full backtrace (driver launched with stdout redirected to a file, per §10.8's own "actionable takeaway"):

md: debugmalloc: attempted to free non-malloc'd pointer 562a0822e780
...
#13  vm.cc:156  remove_destructed_objects()
#14  backend.cc:123  call_remove_destructed_objects()  [the periodic 5-minute GC tick]
...
#6   svalue.cc:187  int_free_svalue() -> dealloc_object()
#5   object.cc:2044  dealloc_object() -> FREE()
#4   debugmalloc.cc:83 debugfree()
#3   md.cc:183  MDfree() -> "attempted to free non-malloc'd pointer" -> abort()

Unlike dtsl's occurrence (which correlated with the §7.12 tell_room() type-crash inside user_dump() itself, already ruled out as present on this lib above), this crash fired from the ordinary periodic 5-minute remove_destructed_objects() GC sweep — nothing to do with this specific player's own net-dead timer, which hadn't even fired yet. This strengthens §10.8's own conclusion that the underlying corruption is a general driver-level object-lifecycle bug triggerable by ordinary long-running gameplay (NPC wandering/dying, temp-object cleanup, etc.), not specific to any one lib's mudlib code — no LPC-level fix is possible or attempted here, matching §10.8's existing verdict. One difference from the prior two occurrences worth noting: this time log/debug.log DID capture the md: debugmalloc:... line (unlike xjcq2000's "debug.log showed nothing whatsoever") — though the full C++ backtrace was still only in the driver's own redirected stdout, so capturing driver stdout during any long-sit session remains the only way to get the actionable detail. Per the task's cleanup instructions and dtsl's own precedent, the raw backtrace file was NOT left in the repo (described here in prose instead); restarted the driver cleanly afterward and confirmed qinfengw's last-saved state (from the earlier clean quit, before this net-dead attempt) reloaded correctly with no corruption or stuck state — the crash cost real uptime but not player data, since nothing autosaves mid-netdead. Recommend escalating to a driver-level investigation per §10.8's own suggested next step, now that a third occurrence with a full backtrace exists.

Explicitly not verified live (say-so per task instructions)

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

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

驱动于 2026-08-12 升级后的重测。标准检查清单(cmds/imm/update.lpc、 cmds/adm/update.lpc 的 §7.106 防护、master.lpc::log_error() 的 §7.10 大小写无关 "arning:" 过滤门、maximum evaluation cost)全部 确认已是正确/安全状态,无需改动;本档案无 adm/daemons/closed.lpc, 不受 §7.107 影响。

新发现并修复的 bug(AGENTS.md 新增 §7.108)

"踢掉重复登录"重连路径导致角色永久无法接收任何指令——现场用两 个真实 telnet 连线复现:先用 fluffos 登录并保持连线不断开,再用同 一账号从第二个连线登录,触发"您要将另一个连线中的相同人物赶出去, 取而代之吗?(y/n)"提示,答 y 后显示"重新连线完毕"、房间内 NPC 环 境对话正常继续刷新,但此后 score/look 等任何指令一律只回应 "什么?"(未知指令),角色实质性瘫痪。

与之对照:单纯"断线不 quit,等驱动判定净断线后重连"这条路径(本项 目 §10.7 checklist 更常规测试的那条)用同一账号复测完全正常——问题 专门出在"旧连线仍活着、主动踢掉它"这条分支。根因追查到 adm/daemons/logind.lpc::confirm_relogin()(y分支)的 exec(old_link, user); destruct(old_link); 顺序——把角色的活跃连线 转移到旧的、即将销毁的 login 外壳物件上后再销毁它,这个"瞬间失去 interactive"的过程似乎连带清空了指令派发表,而随后 reconnect(ob, user) 自己的 exec(user, ob) 只恢复了连线本身,没有 重新注册指令。clone/user/user.lpc::reconnect()(角色类自己的重连 回调)本身也从未呼叫 enable_commands()——净断线重连路径因为角色物 件全程没有真的失去 interactive,所以从未暴露过这个缺口。

修复:在 reconnect() 开头无条件加一行 enable_commands(); (幂等,两条重连路径都安全):

void reconnect() {
  enable_commands();
  set_heart_beat(1);
  ...

验证:重启驱动加载修复后,用完全相同的"保持第一个连线不断开→ 第二个连线登录→答 y 踢掉旧连线"复现步骤,score 修复后立即正常显 示完整角色档案;净断线超时重连路径复测依旧正常(确认没有破坏原本 就工作的分支)。

规模提示,本轮未展开成扫描:grep -rl 'exec(old_link' libs/*/work 命中 140+ 个档案、覆盖相当一部分语料库(经由共享的 logind.lpc 传承/惯例扩散,不是单一血统),但和 §7.106/§7.107 那种 逐字节一致的形状不同,各档案 reconnect() 自己的实现差异较大,不 适合盲目机械扫描——已记录进 AGENTS.md §7.108,标记为未来任何库做 §10.7 时的一个建议检查项(专门测"踢掉重复登录"这条路径,不能只测净 断线超时)。

现场验证摘要

驱动干净启动(build-debug),管理员 fluffos/Mud@2026 登录确认 目前权限:(admin),update /adm/daemons/logind 成功验证真实写入权 限。净断线重连、踢掉重复登录重连两条路径均已现场验证(见上)。 debug.log 全程干净(569 行,无真实错误)。

本轮修改的文件

§7.112 跨库扫描修复(无常 NPC 重连触发重复轮回链)

AGENTS.md §7.100 修复(2026-08-19,批次五)

ROOM 基类冗余 replace_program(ROOM); 自崩溃地雷(详见 AGENTS.md §7.100):2892 个房间文件的 create() 里紧跟 inherit ROOM; 之后 都有这一行多余调用,永久设下"待替换"标记,第一次对该房间对象绑 定闭包就会崩溃。自带建房工具有 2 份拷贝(clone/misc/roommaker.lpc、 u/karlopex/obj/roommaker.lpc),都是"Lonely"三处独立字符串拼接 写法(和 tianxiawuxue 的 u/lonely 工具同款)。另外发现 4 个门派房源 码文件放在 work/data/house/ 下(真正的 .lpc 源码,不是存档),扫 描脚本默认排除 data/ 目录误伤了这批文件——针对这个子目录单独关 闭排除,手动补上(和 xkm 的 work/data/group/ 同一类缺口)。累计 2896 处 live 调用删除,和 survey 记录的数字精确吻合,修复后 0 处 遗留。

验证:build-debug 驱动真实冷启动,端口 40066 正常监听, debug.log 全程干净。既有管理员账号 fluffos/Mud@2026 登录正常 (落地沙滩,look/quit),全程无新增 "cannot replace"/"cannot bind" 日志行。

``§7.112`` residual-gap closure (2026-08-20)

Corpus re-scan (grep -rl 'call_out("death_stage"' ... | filter for missing guard) found unguarded init()-scheduled death_stage() call_out chain(s) in u/lara/wgargoyle.lpc, u/scatter/update/other/npc/wgargoyle.lpc, u/scatter/update/other/wgargoyle.lpc that the original two-wave sweep (see AGENTS.md §7.112) missed -- same reconnect-triggered duplicate-chain bug, different filename/lineage. Added the standard query_temp("death_stage_active")/set_temp/delete_temp re-entry guard, adapted per file's own exit points. Compile-verified via lpcc --batch.

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

深度功能测试第四轮 / Round-four deep functional test (2026-08-20) — resolving the two explicitly-flagged gaps

Booted build-debug clean (zero errors), same config.fluffos (port 40066). Two dual-connection Python socket scripts (one admin connection + one player connection, interleaved, no tmux_mud.sh) used to solve both previously-flagged timing/funding gaps with admin tools, per this pass's explicit instructions. log/debug.log for the whole session: zero 错误/crash/Fatal/denied/cannot/undefined hits (the one compile error that appears in the raw log is my own typo in a scratch eval expression — me() isn't a real efun/simul_efun here — not a mudlib bug; corrected and re-ran).

Item 1 — 纪晓芙 (峨嵋派) sect-join: RESOLVED, real success, no bug found

Root cause of the original timing problem, now understood: she is NOT room-reset-driven (confirmed: this config's time to reset : 1800 is far too long to explain sub-2-second relocations). Her actual wander trigger is kungfu/class/emei/ji.lpc's own chat() (heart_beat → chat() → stay_chance decrements every tick → random_move() once it drops below 1), compounded by her init() unconditionally calling set_heart_beat(1) — since init() re-fires on her every time ANY living thing enters her current room (standard LPC semantics), a plain set_heart_beat(0) from admin gets silently undone the instant anyone (including the admin doing the catching) walks in, explaining why she kept relocating even with heart_beat nominally disabled.

Fix for the test session (not a code change): eval find_living("ji xiaofu")->set("stay_chance", 1000) (freezes her random_move() trigger regardless of init()/heart_beat state), then eval find_living("ji xiaofu")->move("/d/city/guangchang") to plant her in her canonical spawn room. She stayed put through the whole interaction. Restored afterward with eval find_living("ji xiaofu")->set("stay_chance", 0) so normal wandering resumes.

The real bai/recruit flow, exercised live with the existing qinfengw/秦风武 character (male, no prior sect): bai ji alone completed the ENTIRE flow in one command — her attempt_apprentice() (defined via #include "/kungfu/class/emei/sujia.h", not in ji.lpc itself — initially looked like a missing-function gap until the #include was found) ran synchronously, printed her in-character dialogue ("我们峨嵋派的功夫比较适合女弟子修习。既然小兄弟要学些强身 健体的本领,就留在我这里吧。" — a gendered flavor line for a male applicant, not a rejection), then force-issued recruit qinfengw as herself, which matched qinfengw's already-pending bai request and completed the apprenticeship in the same tick. Verified via score: title updated to "峨嵋派第五代弟子 秦风武" (her generation 4 + 1 = 5, correct), 【门派】(sect) = 峨嵋派, 【师承】(master) = 纪晓芙 — all correct. No bug found — the bai→attempt_apprentice→recruit round trip works exactly as designed; the earlier pass's inability to catch her was purely a scripted-testing timing limitation, now solved with the documented admin-freeze technique. Not touched: sujia.h, ji.lpc, bai.lpc, recruit.lpc, feature/apprentice.lpc — all read, all correct.

Item 2 — successful shop purchase: RESOLVED, real success, no bug found

Used the sanctioned admin clone+give pattern for this lib's real coin object (/clone/money/coin, COIN_OB): admin goto qinfengw, clone /clone/money/coin (clones 1 into admin's own inventory, default set_amount(1) per coin.lpc's create()), eval present("coin", find_player("fluffos"))->set_amount(50) (bumped to 50 copper), give qinfengw coin — first attempt silently failed ("对方不接受这样东西") because qinfengw was still netdead (<断线中>) from the previous test connection being closed without quit; give.lpc's do_give() only auto-succeeds for interactive() targets, otherwise it tries an NPC-oriented TASK_D->task_give() / accept_object() path that a plain netdead player object doesn't implement. Not a bug — reconnected qinfengw properly (a real player would just be logged in) and the identical give command succeeded instantly ("你给秦风武五十文铜钱。").

qinfengw then walked the real path from 中央广场 to 鲜花店 (north → 客店, west → 客店, west → 鲜花店 — confirmed live, not guessed) and ran the real list/buy flow: list showed the correct 14-item price sheet (情人草 = 十五文铜板/15 copper, matching qingren_cao.lpc's set("value", 15)); buy 1 qingren cao succeeded ("你向英莲买下一株情人草。"); i confirmed both halves of the transaction: exactly 35 copper remaining (50 − 15, correct deduction via MONEY_D->player_pay()) and the real item in inventory ("一株情人草(Qingren cao)"). No bug found — hua_girl.lpc's do_buy()/MONEY_D->player_pay() price/deduction/item-move logic is all correct; the only reason this was never reached before was the test character's lack of funds, now solved with a real admin-funded purchase per this pass's instructions.

Checklist sanity pass (fast, confirm-only — all already correct)

Files touched this pass

None (no code changes — both gaps resolved as genuine successes, not bugs). Save-file churn only: work/data/{login,user}/{f/fluffos,q/ qinfengw}.o (admin coin-clone + qinfengw's new sect membership/ inventory), work/tmp/tmp_eval.lpc (routine eval-command scratch file, already tracked from prior passes).

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: 6 line(s) in 3 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 shenzhou: 482 distinct diagnostics at the end, 12506 files compile and 207 do not. A load check of every .lpc (12742 files, 700 per VM boot) at HEAD and in the working tree: 12476 PASS -> 12497 PASS, no file that loaded before fails now except one random draw (below). 1999 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 shenzhou 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; nothing written new): d/shaolin/shaolin/obj/putao.lpc (::init(); in an ITEM+F_FOOD object whose parents have no init()); d/baituo/barn.lpc (a stray : in front of the // of the second line, the same in the archive's own file and in zhonghua2, zhyx); d/huanghe/doc/set_bang.h (two answers end in unrecoverable characters; both strings are closed after 帮主的景仰之情, as in the root: d/huanghe/npc/bangzhu.lpc loads again). The load check's one flip, u/haihe/naihe_b.lpc, picks a random location and fails when it picks /u/spark/spark_room, which this lib does not have.

What is left: dead files (the ftp/ and u/ wizard trees, drafts, pasted logs, the TMI-2 network daemons without headers); d/wuguan/zhengting.lpc (ends inside a /* comment), d/dali/npc/fy-skset1.h (skill_set2 defined twice), kungfu/class/kunlun/hetaichong.lpc, clone/gift/pantao.lpc (a call left without its closing );), d/shaolin/haishui3.lpc (yaoshui.h never shipped), and d/heimuya/basket.lpc (an ITEM that calls ::reset() and the undefined check_trigger()).

深度功能测试(§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 shenzhou: 53 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 71 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): 14 PASS -> 65 PASS, 0 that loaded before fail now, 51 more load.

The 53 files are 52 .C -> .lpc (the tea items of d/city/npc/tea, the Lingjiu array rooms bagua*, items of d/lingjiu/obj, rooms of d/shaolin ...) and d/lingjiu/BAGUA.H -> bagua.h. Rescan: 474 distinct diagnostics, 12562 files compile and 203 do not (12506 / 207 / 482 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-11):“无副作用表达式”警告复查(批 3)

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

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

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

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

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

scripts/toolkit_fix.sh 一轮(冗余继承 5 处)加手工收尾,能编译的文件警告清零。改动的对象加用到告示板的若干房间 HEAD 与工作树各载入一次:70 个,HEAD PASS 69,tree PASS 69,regress 0。

内力/精力当作伤害种类(2026-10-15,KB 06 §7.241 第二类)

本库 feature/damage.lpc 的 receive_damage() 不接受 jingli/neili/force 等种类,me->receive_damage("jingli", 30) 之类在练功、绝招、吸取时报 F_DAMAGE: 伤害种类错误。1 处改为 add(type, -N)(scripts/lpc_fix_damage_calls.py 第二步)。

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

mapping fam = ob->query("family"); 之后直接 fam["family_name"]:没有门派(新人或叛师后)的玩家在学习、练功、拜师、进入门派房间时报 Value being indexed is zero,而不是作者写好的提示。1 处在声明行加上 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))(走火入魔扣上限)等本库不接受的种类,1 处改为 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/sansha、damo-jian/weituo、huashan-jianfa/feilong、huashan-jianfa/kuaijian、huashan-jianfa/yijian、jinshe-zhuifa/huayu、jinshe-zhuifa/tuwu、liangyi-dao/sanshen、mo-bang/mo、quanzhen-jian/lianhuan 等 18 个。

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

经验、属性、内力上限不是技能,xkx/ES 的 query_skill() 对它们只返回 0(风云的还会去载入同名技能文件而报错)。改为 query("...")(scripts/lpc_fix_pool_as_skill.py):驯马的 /d/dali/npc/a、/d/dali/npc/mafu、/d/newdali/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):xiaowu-xiang→xiaowuxiang(liuyang-zhang、liuyang-zhang/qufu、liuyang-zhang/zhongfu、ruyi-dao/lianhuan、tianyu-qijian/san);shenhuo-xuanming→shenghuo-xuanming(/d/mingjiao/skill/dafeng-yunfei、dafeng-yunfei、xiaoyao-zhangfa);lingboweibu→lingbo-weibu(/kungfu/class/lingjiu/tbhao);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 读到只有文件头的 map.o(2026-10-15,KB 06 §7.250)

本库的 data/npc/map.o 只有一行文件头:genmap 第一次载入时 if (!restore()) save();,地图还没建就存了档,值为 0 的变量不写进去。mapdb 的 restore() 读它成功,map 却成了 0,query_room_info()、query_room_exits()、query_map() 都对 0 取下标或把 0 交出去:临济庄「幽冥桩」、福威镖局的护镖任务(ftb_zhu)、jinhe、八卦阵等用到地图的地方都报错。mapdb(/clone/obj/mapdb.lpc)加一个 restore():读档后 map 不是映射就重置为 ([]);地图为空时 query_random_room_path() 返回 0。地图本身仍需巫师用 genmap 生成。

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

/d/city/npc/ftb_zhu.lpc、/d/qilian/obj/jinhe.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、/d/hengshan/npc/npc_setup.lpc:level = pow(10 * exp, 0.333334); 把浮点数隐式截成整数,编译时警告;改为 to_int(...),结果不变。

用兵器的绝招空手施展时报错(内联写法)(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、jueqing-bian/sanwu、liangyi-jian/wushengse、tangshi-jian/luanci。

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

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

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

11 个文件、14 处在 create() 里 carry_object("<本库没有的文件>")->wear()(或 wield()、set_amount()):carry_object() 得 0,接着的调用报 Bad argument 1 to EFUN call_other(),NPC 根本载入不了,该有它的房间里就少了这个人。多是从别的系统搬来的 NPC 用了原系统的物品路径(最常见:/clone/misc/naihe/other/obj/dress、/d/changan/npc/obj/yayifu、/u/jiuer/obj/greenrobe)。每处改为 { object carried_ob = carry_object(...); if (carried_ob) carried_ob->wear(); }(scripts/lpc_guard_carry_object.py):NPC 照常出现,只是不带这件东西;文件补回来(或是虚拟对象)照样会带上。载入检查:改动的文件改前能载入 0 个,改后 11 个(11 个原来载入不了),没有退步。

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

feature/skill.lpc 里的 set_skill()、map_skill()、prepare_skill() 碰到没有武功文件的名字就 error("F_SKILL: No such skill (…)"),调用它们的 NPC create() 整个中断:NPC 载入不了,刷出它的房间也少一个人。本库有 2 个对象设了本库没有文件的武功(3 个名字,最多的是 songyang-qigong、songyang-zhang、jiushou-jiushi)。三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。抽样载入这些对象:2 个里 1 个通过,其余 1 个停在别的错误上(d/hengshan/npc/npc_setup:*No program in object '/d/hengshan/npc/npc_setup'!)。(scripts/lpc_guard_set_skill.py)

通向不存在房间的出口,按对面房间的回头出口改正(2026-10-18,KB 06 §7.257)

房间出口 "west": __DIR__"xxx" 指向的文件本库没有,玩家走过去就被卡住(笔误、改过名的房间,或者没带进档案的编号房间)。对面的房间存在、并且它相反方向的出口正好指回这个房间,说明出口原本就是通向它:/d/mingjiao/didao/didao 的 northeast:/d/kunlun/shanqiu → /d/mingjiao/shanqiu;/d/murong/qing3 的 east:kufang → nanshi;/d/wuguan/houshan1 的 southwest:xiaolu1 → huayuan。改动后每个出口都能解析到本库的文件(scripts/lpc_fix_dead_exits.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 映射不到,门派弟子用普通轻功/拳脚)。按核对过的对照表改正,共 23 处、21 个文件:dashouyin→dashou-yin(6 处)、lingji-zhuang→linji-zhuang(6 处)、sanying-zhua→sanyin-zhua(5 处)、pudu-chang→pudu-zhang(2 处)、huagu-mainzhang→huagu-mianzhang(1 处)、banrou-zhang→banruo-zhang(1 处)、lingboweibu→lingbo-weibu(1 处)、bahuangh-gong→bahuang-gong(1 处)。(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 文件。

两个斜杠开头的物件路径(2026-10-19,KB 06 §7.276)

1 个文件、1 处物件路径写成 //…(如 carry_object("//clone/cloth/hic_cloth")):/u/final/workroom.lpc。当年的驱动不介意(路径先被规范化);新驱动把原样的字符串交给 master 的 valid_read(),securityd 按 //clone 查目录,NPC 的 euid 没有权限,报 Read access denied.,NPC 载入不了。改成一个斜杠。

帮派文件里丢掉的引号(2026-10-19,KB 03 §4.6)

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 处)、/kungfu/skill/huashan-jianfa/kuaijian.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,共 43 处)。改动:/d/changan/kezhan.lpc(1 处)、/d/city/jiuguan2.lpc(2 处)、/d/city/muoult.lpc(1 处)、/d/city/npc/mj.lpc(3 处)、/d/city/npc/mjxiaosi.lpc(3 处)、/d/city/npc/muou.lpc(1 处)、/d/city/wumiao.lpc(1 处)、/d/city/zuixianlou.lpc(1 处)、/d/heimuya/jiudian.lpc(1 处)、/d/jueqing/jianfang.lpc(1 处)、/d/jueqing/liguifeng.lpc(1 处)、/d/jueqing/maowu.lpc(1 处) 等 24 个文件。

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)。

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

shenzhou:武当知客 greeting()、西夏使者 assign_job()(2026-10-21,KB 06 §7.348)

zhike.lpc:提前返回的判断 !living(ob) || strsrch(file_name(where), ..) 读 ob、where,赋值在判断下面:每个来客都抛错,知客的问候从未出现。把赋值提到前面并判 where 为空。xixiashizhe.lpc:派任务的最后一句 ob->set("owner", ..),ob 没赋值(xkx2001 的原文那里发的是 ypling 令牌,shenzhou 的这个使者不发令牌),删掉这一句和 object ob。改动:/d/qilian/npc/xixiashizhe.lpc、/kungfu/class/wudang/zhike.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.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 18 条注册(12 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/d/beijing/npc/duolong.lpc(do_jobwith)、/d/beijing/obj/kuanglu.lpc(do_look)、/d/mingjiao/npc/mingjiao_npc.lpc(do_qingjiao)、/d/quanzhou/npc/gongping-zi.lpc(do_bihua、do_halt)、/d/shenlong/obj/sg_mianzhao.lpc(do_open)、/d/xiakedao/obj/car.lpc(do_hire)、/d/xingxiu/obj/box.lpc(qing)、/d/xingxiu/obj/sanxiao.lpc(throw_ob2)、/inherit/misc/fboard.lpc(do_list)、/kungfu/class/huashan/buqun.lpc(do_answer) 等共 12 个文件。最多的函数名:do_bihua 3 处、do_qingjiao 2 处、do_halt 2 处、do_hire 2 处、do_answer 2 处。没有动(读过):/clone/obj/job/job_protect_npc.lpc 的 do_baohu(对象本身编译不过,登记留着,等对象能加载再处理)、/d/xingxiu/obj/box.lpc 的 qing(不是单独一行的语句(obj->add_action()、宏或没有分号的写法))。

注册到不存在函数的 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.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 30 条注册(13 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/clone/misc/naihe/dc_fight.lpc(do_outhere)、/clone/misc/naihe/other/dc_fight.lpc(do_outhere)、/d/city/muoult.lpc(do_enable、do_guo)、/d/jueqing/changlang3.lpc(do_open)、/d/taishan/tingyu.lpc(do_exercise)、/d/wuguan/cliff2.lpc(do_search)、/d/wulin/jiulu.lpc(do_exercise)、/d/wulin/shishe.lpc(do_exercise)、/d/wulin/tingyu.lpc(do_exercise)、/d/xingxiu/xueling/exit1.lpc(do_ling) 等共 13 个文件。最多的函数名:do_exercise 8 处、do_outhere 4 处、do_enable 2 处、give_job 2 处、do_cut 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.354)

MACRO->函数() 调用的函数在这个库的守护进程里不存在:call_other() 对不存在的函数不报错、只返回 0,所以这些功能(时间显示、昼夜事件、名字检查、价格文字……)一直没起作用。同一血统另一个库的同名守护进程里有这些函数,原文照抄到本库守护进程末尾(scripts/lpc_port_function.py;调用处传的参数个数、函数体用到的函数 / 全局变量 / 宏 / 字符串键都在本库里有,已核对;守护进程在 HEAD 和工作区都能加载,每个函数用固定参数各调用一次:HEAD 返回 0,现在返回正常的值):/adm/daemons/chinesed.lpc ← aoxiangtianji:chinese_period()(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。 还剩 8 个函数没有补(chat_msg_combat 6, inquiry 2):原型还在函数体丢了、或者根本没写过,没有可以直接补回的捐赠(别的库同一路径的文件里没有这个函数,或者它还要用本库没有的辅助函数、签名不同),不编造:/d/emei/npc/guide.lpc:ask_job_begin、/d/emei/npc/guide.lpc:ask_job_finish、/d/xiakedao/npc/huang.lpc:auto_perform、/d/xiakedao/npc/laozu.lpc:auto_perform、/kungfu/class/murong/baichuan.lpc:auto_perform、/kungfu/class/shenlong/pang.lpc:auto_perform、/kungfu/class/shenlong/shou.lpc:auto_perform、/kungfu/class/shenlong/yin.lpc:auto_perform。 另有 3 处函数体被作者整段注释掉(指针没跟着删),是有意关掉的,没动。

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

这个库的 1 个文件里有 1 处 write_file("/log/nosave/...") / log_file("nosave/...")(static 改 nosave 时连字符串里的日志路径一起改了,原来是 log/static/):/log/nosave/promotion_log.lpc 等。压缩包和站点包里没有这个目录,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 的新进程):补目录前报上面的错误,补后写成功。

读不到的留言板:"/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 把 3 条单独成行的语句(/clone/board/builder_b.lpc、/clone/board/doctor_b.lpc、/clone/board/team_oneb.lpc;/newjob/builder/meeting.lpc、/newjob/doctor/meeting.lpc、/newjob/tiejiang/meeting.lpc)注释掉并写明原因(别的房间里同样的语句本来就是作者自己注释掉的)。证明和 45 个库的整体加载检查见 KB 06 §7.357。

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

这个库的代码往 log/ 的 2 个子目录写日志:log/beijing(d/beijing/helper.lpc)、log/debug(d/huanghe/npc/guanjia.h)。压缩包、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/debug 记进 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: 函数名,这一拍心跳的后半段被跳过。这些函数没有可以补回的来源(别的库同一路径的文件里没有,或者它还要用本库没有的东西),所以 7 个文件里 9 行只含这个指针的行((: auto_perform :), "心法": (: ask_me :), "苏习之": (: ask_su :),)加了注释,原文保留并写明原因,这一项当作没有这个话题 / 物品 / 闲聊条目(scripts/lpc_fix_dangling_lfun.py;#include 的文件在库里缺失的对象、指针不在整行上的写法没动)。 前提(function_exists())和加载检查(HEAD 与工作区对比,本库改动的 .lpc 文件)的结果见 KB 06 §7.355:没有一个回归。

汉字按字节切片的老写法没有换成按字符:改法名 "澄" + name[2..3]、辈分判断 name[0..1] == "澄" 等 145 处(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 的长度检查按字节算,合法的名字一个也过不了。

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

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

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

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