Divine Land (shenzhou)

✅ 可玩

神州

shenzhou

🔑 fluffos / Mud@2026 更新 85d6a35 2026-09-02 源码 下载 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/ dbasefeature/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\bnosave 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 directorylibs/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 outrightss/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.lpccmds/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.lpcu/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 baiattempt_apprenticerecruit 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 coinfirst 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 foundhua_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).