info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
大唐西游之紫藤站(登录后横幅也作【紫藤西游】),属于 ES II 引擎系列(与 `es1_win`/`xkx2001`/`rzrmud` 等同源,master 文件署名同样能追溯到 "for ES II mudlib, original from Lil, rewritten by Annihilator"),与本项目"地狱"/Doing 血统档案(`zjdy2008wzb` 等六件套)逐一比对地图后确认二者几乎没有重叠,属于彼此独立的分支;世界观把《西游记》色彩的三十三天、高老庄、南海等场景嫁接在标准金庸门派地图(华山、峨嵋、昆仑等)之上,新角色需先在体格、根骨、悟性、灵性四项属性间分配天赋点,再从南城客栈起步闯荡江湖,巫师登录除普通登陆密码、管理密码外还要求第三重"巫师专用密码"。
English
Great Tang Journey West — Purple Wisteria Station (its in-game banner reads 'Purple Wisteria Journey West'), an old-school wuxia/xianxia MUD that grafts a Journey-to-the-West-flavored backdrop (zones named for the Thirty-Three Heavens, Gao Village, the South Sea) onto a standard Jin Yong sect map (Huashan, Emei, Kunlun and more). Part of the ES II engine family, sharing common ancestry with this collection's es1_win/xkx2001/rzrmud and likewise signed 'for ES II mudlib, original from Lil, rewritten by Annihilator' in its master file — a room-tree file comparison against this batch's separate 'Hell'/Doing-lineage archives (zjdy2008wzb and siblings) found essentially no overlap, confirming it's a genuinely unrelated branch despite superficial similarities. New characters distribute talent points across four stats (Physique/Bone-Root/Comprehension/Spirit) before starting at the South City Inn, and wizard logins require a third 'wizard-only password' layered on top of the normal login/admin password pair. Gameplay includes a pawnshop, NPC combat, an automatic death/revival dialogue where Judge Cui Jue restores the character, and a bulletin board.
README
内容亮点
- 以《西游记》世界观为背景的老牌武侠/仙侠 MUD,"大唐西游"这个招牌在 本项目处理的多个压缩包里都出现过,本作是其中"紫藤站"这一具体分支/ 服务器实例。
- 新角色创建后会经历"体格/根骨/悟性/灵性"天赋点分配的小游戏,随后进入 以"南城客栈"为起点的江湖世界,整体内容和游戏系统比较完整。
- 巫师登录除普通密码外,还要求一个第三重"巫师专用密码"。
- 深度测试实际走完了移动、逛当铺、和游荡 NPC 打了一场完整战斗、被 打死后自动触发的死亡/复活对话流程(判官崔珏自动送还阳),以及留 言板阅读——全程
debug.log保持空白,是本项目里少数经过深度游玩 仍未发现新 bug 的档案之一,详见 NOTES.md。
在线试玩
https://mudlibs.fluffos.info/zitengzhan/
管理员账号 / Admin account
- ID:
fluffos中文名: 浮浮 - 登陆密码(日常登录用):
Mud@2026 - 管理密码(找回/改密用):
Admin@2026 - 巫师专用密码(巫师登录时额外要求的第三个密码,与登陆密码不能相同):
Wiz@2026 - 权限:
(admin)("总管巫师",最高级;通过adm/etc/wizlist授予)。
警告:这是一个公开的默认密码,仅供本地/浏览器试玩。正式对外开服前
请务必修改这三个密码。
本地运行
cd libs/zitengzhan
~/src/fluffos/build-debug/src/driver config.fluffos游戏端口:40071。
NOTES · 移植与修复记录
zitengzhan — 紫藤站 (archive #77, 紫藤站.rar)
What this lib is
Root dir inside the archive is ztxy2013/ (self-named "ztxy" = 紫藤西游).
include/mudlib.h gives the full game name: MUDLIB_NAME "大唐西游之紫藤站"
("Journey to the West of the Great Tang — Wisteria Station"), branded
in-game as 【紫藤西游】/大唐西游·紫藤站. Config's own name field
(after encoding fix) is 紫藤西游; live banner during testing showed both
"大唐西游·紫藤站" and "紫藤西游" — same game, matches the slug/archive title
(紫藤站) closely enough that no slug-vs-title note is needed.
adm/obj/master's header credits "for ES II mudlib, original from Lil,
rewritten by Annihilator (11/07/94)" — this is the same "ES II" engine
lineage as es1_win/esI/xkx2001/rzrmud/bmxkx2001/
kxkj/yueyingqiyuan/wuhanzhan/yanhuangwuhun/haiyang2/
kxkj1/huoying/yhyxs/shenzhou/shenmo — one of
the largest lineages processed in this project. Layout is adm/obj/ +
adm/daemons/ (not the adm/single/ or adm/kernel/ variants), matching
es1_win/xkx2001/rzrmud most closely. Large lib: 189MB raw / 14,643 .lpc
files after conversion (comparable to xiakexing100's 14,227).
NEW finding this pass: an extensionless "live" file coexisting with a
stale same-named .c/.C backup can make the WRONG one authoritative
after the .c→.lpc rename — check every lib for this going forward
This archive has an unusually large number (35) of file pairs where a
real, actively-edited object with NO extension at all (e.g.
adm/obj/master, adm/daemons/securityd, feature/command,
feature/damage, adm/simul_efun/message, cmds/usr/score3, ...) sits
right next to a .c-suffixed sibling (master.c, securityd.c,
command.c, ...) that is sometimes a stale earlier backup and sometimes a
newer rewrite — the direction isn't consistent, it has to be checked
per-file. Both the config file's master file/simulated efun file
directives AND the lib's own macros (SECURITY_D, etc.) name the
extensionless path with no suffix at all.
Per this driver's real resolution rule (confirmed by reading
~/src/fluffos/src/vm/internal/simulate.cc's load_object()):
extension-less names are resolved as .lpc first, .c as a fallback —
there is no third attempt at a literal zero-extension filename. So once
convert_lib.sh renames master.c → master.lpc, that renamed backup
silently becomes the ONLY thing the driver will ever load for
master file : /adm/obj/master, even though a separate, sometimes-more-
correct master file with no extension at all sits right next to it,
completely untouched by the whole pipeline (its glob is *.c, so it's
invisible to every mechanical fix pass — encoding, .c"-reference-fixing,
static→nosave, all of it).
Found via this: adm/obj/master.c (→ master.lpc) had TWO real bugs
not present in the extensionless master:
1. A stray return; right after the local-variable declaration at the top
of log_error(), silently making the whole function a no-op (dead
code — no logging, no player-facing error text, ever). Removed.
2. preload() used an old call_other(file, "??") force-load trick
(works, but always throws a catchable "Undefined function" error per
daemon, spamming the boot console for no benefit) instead of a plain
load_object(file). Restored to load_object(file).
Went the OTHER direction for other pairs in this same archive:
feature/message (.c had the fuller message_vision(msg,me,you,view,
shield) signature with shield/view support — the extensionless copy was
the older simpler one), cmds/usr/score3 (.c was "rewritten by huarong
2004/2", clearly superseding a <SecCrypt CPL V3R05>-protected older
extensionless original), obj/mailbox (trivial one-line difference, .c
version has an active CONVERT_D->input() call the other has commented
out). securityd.c/securityd: .c version (→ securityd.lpc, the
one actually loaded) has a strictly larger ACL (extra d/obj/newitem
excludes, a jznt→(admin) special case, a PLAYERCITY_FILE_DIR allow)
with no functional regression versus the extensionless copy — kept as-is.
Recommendation for AGENTS.md: add this as a new catalog entry. The
check is cheap: find <raw-root> -type f ! -name "*.*" | while read f; do
[[ -f "$f.c" ]] && echo "PAIR: $f <-> $f.c"; done (exclude u/,
binaries/, backup/, OBJ_DUMP/). For any hit that's on the boot/
registration critical path (master, securityd, simul_efun, logind, or
anything #included/inherited by them), diff the two by hand and decide
which is genuinely correct — don't assume either direction (".c is
older" or ".c is newer") holds consistently even within one archive.
Fixes applied (with why)
- §15h (GBK byte-range Chinese-name check):
adm/simul_efun/chinese.lpc'sis_chinese()usedstr[0] > 160 && str[0] < 255(GBK lead-byte range) andstrlen(str)>=2— replaced with a CJK Unified Ideographs codepoint check (str[0] >= 0x4e00 && str[0] <= 0x9fff) and halved the length floor to>=1.adm/daemons/logind.lpc'scheck_legal_name()had the matching sliding-window bug:i%2==0 && !is_chinese(name[i..<0])(byte- offset gate, only checking every other position) — dropped thei%2==0 &&gate so every character position is checked; halved the length bound from<2 || >12(bytes) to<1 || >6(chars), matching what the message text already promises ("必须是一到六个中文字" = 1 to 6 Chinese characters). A THIRD, separate combined-length guard inget_name()(strlen(arg) > 8) was also halved to> 4(the "how many total characters the whole name may be" cap, tighter than check_legal_name's own 6-char ceiling — original intent was clearly 4 real characters at 8 GBK bytes). - §3/§15z:
include/globals.hhad a#ifndef __SENSIBLE_MODIFIERS__ / #define nosave static / #define protected static / #endifcompat shim.__SENSIBLE_MODIFIERS__is never defined by this driver, so the shim was always active; the blanketstatic→nosavesed rewrote it to#define protected nosave, silently aliasingprotectedtonosave. Neutralized the whole shim with#if 0(both keywords are real, correctly-behaving, and need no translation on this driver). - §3 counterexample: 68 files hit the exact
log_file("static/X", ...)→log_file("nosave/X", ...)collision (a lib-specific log-subdirectory naming convention, unrelated to the keyword) —log/static/andlog/monitors/already existed as real seed data on disk. Reverted all 68 occurrences back to"static/...via a scoped sed (s/"nosave\//"static\//gon every file that matched), verified zero"nosave/string-literal hits remain andprotected void crash(...)in master.lpc is untouched. - §14 (
valid_override2-arg → 3-arg): upgradedmaster.lpc'svalid_overridetovarargs int valid_override(string file, string name, string main_file), also trustingmain_file == SIMUL_EFUN_OB || main_file == MASTER_OB— confirmed via the lpcc sweep that this matters foradm/simul_efun/object.lpc/oo.lpc'sefun::destruct(ob)call when compiled STANDALONE (the fragment's ownfile!= SIMUL_EFUN_OB), though the REAL boot'ssimul_efun.lpcload already went through clean either way (see §6b note below). - §8d/§15o (
get_include_path()insurance): master had none at all; added the standard prepend-including-directory +:DEFAULT:fallback, proactively, before any failure was observed. - §15w (log_error warning-spam gate):
master.lpc'slog_error()showed either the full compiler trace (wizard) or the generic scary default error message (non-wizard) tothis_player(1)for EVERY message reaching this apply, including plain compile *warnings* — gated the player-facing echo on the message NOT containing"warning:"(still logs everything tohome+"log"regardless). Narrower blast radius than wuhanzhan's version (this one only ever messaged the one causing player, never broadcast to everyone online) but same underlying gap. - §8h (convertd.lpc Greek-table typo): 45 occurrences of the stray trailing-backslash-before-closing-quote pattern (
"X\",→"X",), fixed with the standard sed. File had plain LF endings, no CRLF workaround needed. - §15t (absolute path in
#include <...>):death/npc/heiwuchang.lpchad#include </d/panshi/panshi.h>— converted to quotes. The header itself doesn't exist anywhere at the referenced path (only a same-named file survives under an unrelated wizard's personal directory,u/lying/obj/panshi.h) — a genuine §13-style missing-content gap, not fixed further; this NPC isn't preloaded or reachable from the registration path. - §15ac (bare
SAVE_EXTENSION): 2 files (adm/daemons/network/netmail.lpc,u/tianlin/wiz/token.lpc) used the bare macro instead of__SAVE_EXTENSION__— fixed both (low priority: neither is preloaded or on the critical path, one is intermud/network code, the other a personal wizard tool). - Uppercase
.Cfiles (§15h/shenmo precedent): 183 files, all underarmor/,clone/bq/, etc. —convert_lib.sh's glob only matches lowercase.c; manually found and renamed all 183 to.lpc. No quoted string references to the uppercase names existed anywhere (verified via grep), so no reference-fixing needed beyond the rename itself. - Pre-existing typo,
logind.lpc:129:if( (ip_number[i] != '.') && (ip_number[i]>'25') )— a multi-character literal'25'is illegal on this driver's grammar (single-char constants only), a hard compile error that took down the entire preload chain (logind is preloaded). Clear intent from context (validating an IP-number string as digits-and-dots only) — fixed to the single-char'9'(reject anything that's neither a dot nor a digit). - Two isolated BIG5-encoded files (found via
convert_lib.sh's "FAILED to convert" list, distinct from the archive's otherwise consistent GBK/GB18030 encoding):d/sea/npc/beast1.c(a genuine NPC, BIG5 content with one stray trailing garbage byte\xff\xbapast the final}) andd/obj/quest/shuijingqiu.pujing(an orphaned, genuinely truncated/corrupt file with no references anywhere in the tree,.pujingextension so never compiled regardless). Both converted withiconv -f BIG5 -t UTF-8 -c(drops the trailing invalid byte);beast1renamed to.lpcand is real, working content;shuijingqiu.pujingleft with its original odd extension since nothing loads it.
Confirmed NOT needed (and how)
- §4 (lazy
load_objectinvalid_read/valid_writerecursion):master.lpc'svalid_read/valid_writeonly callfind_object (SECURITY_D)— neverload_object()— so the recursion trap can't fire. Confirmed by reading the actual function bodies. - §7 (missing
get_root_uid/get_bb_uid): both present inmaster.lpc, returningROOT_UID/BACKBONE_UIDrespectively. - §15n (custom securityd ACL blocking mid-connection lazy compiles):
securityd.lpc'svalid_readearly-allows everything whosefuncISN'T one ofread_file/file_size/stat/read_bytes/tail/ed_start—load_object/recompile_object/includeall fall through to that earlyreturn 1, so the gap this bug class needs never opens here. Confirmed by reading the function; also empirically confirmed by the clean boot (every never-preloaded daemon the registration flow touches compiled/loaded without a single "Read access denied" indebug.log). - §15l (master destructing SIMUL_EFUN_OB):
master.lpc create()only doeswrite(...)— nodestruct()call at all. - §15 (nitan-family simul_efun dbase architecture bug):
feature/ dbase.lpcimplements real localset/query/delete/add(+_tempvariants) methods against its owndbasemapping — not a bare simul_efun call relying onthis_object(). Confirmed by reading the file directly; this lineage was never nitan/kernel-family to begin with. - §15p (DNS/intermud daemon preload hang):
adm/etc/preloadhas nodns_masterentry at all (confirmed bycat); a fullnetwork/ dns_master.lpcDOES exist on disk but is never referenced fromlogind.lpc's real registration-flow functions (only from unrelatedu/-personal backup copies oflogind.c) — confirmed via grep, so §15ab's "called inline even when excluded from preload" variant doesn't apply either. - §15ae (
private nomaskcommand-hook silently breaking every post-login command):feature/command.lpc'scommand_hook()(the one actually wired toF_COMMAND, confirmed via the macro) is declared plainnomask int command_hook(string arg)— theprivatemodifier is present only as a COMMENTED-OUT line directly above it (// private nomask int command_hook(string arg)), i.e. this lib's own history already dropped it, matching the already-fixed state seen on shenmo/ shenzhou. Confirmed empirically too:look/scoreboth produced real output post-login (transcript below). - §15ag (
ed_start/ed_cmd/query_ed_mode,__OLD_ED__build): grepped the whole tree — the only hits for these three names are insidesecurityd.lpc'svalid_read, comparing thefuncSTRING parameter against the literal"ed_start"(an ACL case, not an efun/apply call at all). No lib code anywhere calls the realed_start()/ed_cmd()/query_ed_mode()applies. - §15ah (missing save-data directory silently swallowing a
write_file/log_fileerror): everylog_file()path referenced from the boot chain / registration flow (static/CRASHES,monitors/login,monitors/wrongpass,monitors/wizpass,FILES,efun,read_files,user_count,counter_for_web,band) resolves to either the log root or an already-existing subdirectory (log/static/,log/ monitors/, both present in the raw archive). Player-save paths (/data/user/<letter>/<id>) go throughfeature/save.lpc'sassure_file()(a real simul_efun thatmkdir()s every missing parent directory before saving) — confirmed empirically too:data/user/q/ qinhan.oetc. were created correctly for every test registration. - §15s (
tell_room/message()4th-arg raw-int-0 bug):adm/ simul_efun/message.lpc'stell_room(mixed ob, string str, object *exclude)declaresexcludewith the correctobject *array type (not a bare untyped/int parameter), so an omitted 3rd arg resolves to a typed-array nil rather than a raw literal0the way the buggy yueyingqiyuan-family wrapper did. No "Bad argument 4 to message()" ever appeared indebug.logacross 4 full test sessions (including a live room, NPCs greeting the player, and heartbeats firing). - §15u (dormant phone-home/license-check destructor): grepped
securityd.lpc/master.lpc/all ofadm/daemons/forshutdown(/rmdir("/"/rm("/")-shaped destructive patterns gated on an opaque check — only hit wasprofiled.lpc's harmless single-filerm("/PROFILE")(a legitimate profiling-log reset, not a licensing gate). - §15x (hardcoded
MUD_PORTmismatch):include/getconfig.h'sLOCAL_PORT()macro is(int) get_config(__MUD_PORT__)— reads the REAL live driver-assigned port dynamically, not a baked-in constant. Confirmed via source read; no port-mismatch connection rejection is possible here.
Registration-flow AND post-login-command transcript/outcome
Read adm/daemons/logind.lpc's full input_to callback chain before
scripting the test (per the standing policy) — the actual shape is:
logon() → encoding() (gb/big5) → if_login() (1=enter/2=exit) →
get_id() (English id; "new" branches to registration) →
get_new_id() (new English id, auto-confirms via confirm_id("Yes",ob)
with NO separate y/n step) → get_name() (Chinese name) →
get_super_password() → confirm_super_password() → new_password() →
confirm_password() → get_email() → make_body() + get_gender()
(m/f) → confirm_gift() → enter_world(). Not documented in
logind.lpc itself, discovered live: enter_world() drops the new
player into a SEPARATE gift/stat-allocation UI (体格/根骨/悟性/灵性,
choose [0-3] to re-roll one stat or [9] to accept defaults, followed
by a y/n confirmation) before the player is actually free to type
ordinary commands — my first test run sent look/score/quit right
after gender and they were silently absorbed as no-op answers to this
stat menu (each producing the same re-prompt) since none matched 0-3/
9/y/n. Second attempt inserted 9 + y before the post-login
commands and worked correctly.
Full successful transcript (one continuous mudclient.py connection,
--send "gb" "1" "new" "qinhan" "秦汉" "Abc123456" "Abc123456" "Xyz65432"
"Xyz65432" "[email protected]" "m" "9" "y" "look" "score" "quit"):
gb→ GB encoding selected, full banner/MOTD shown.1→ enters the id-collection flow, shows site-list text.new→ "请您给自己取一个英文名字:" (give yourself an English name).qinhan→ accepted as a new English id.秦汉(real Chinese name, 2 characters) → accepted, moved straight to the admin-password prompt (confirms §15h's fix works end-to-end, not just in isolation). One pre-existing cosmetic bug noted here: right after acceptance,get_name()still has an original debug leftoverprintf("%O\n", ob);("by canoe for suppwd") that echoes the raw object reference (/obj/login#0,/obj/login#6, ...) to the connecting client — harmless (doesn't block anything) but ugly; left as-is per breadth-over-depth, noted here for whoever revisits this lib.Abc123456(×2) → accepted as the admin/"super" password (needs both upper+lowercase letters plus at least one digit/symbol, >6 chars).Xyz65432(×2) → accepted as the everyday login password (≥6 chars, differs from the super password).[email protected]→ accepted as email (needs an@and a.).m→ male gender selected.9theny→ accepted the default gift/stat allocation.- Reached the actual game world: start room
南城客栈(South-City Inn) with full room description, exits (west/east/up/north/south), board, and 4 real NPCs listed (新手指导/Newbie npc, 店小二, 邮差千里眼, 唐三藏, 环境判官) — one NPC (新手指导) proactively greeted the new player with a welcome tell. score→ produced a full, correctly-formatted character sheet (仙衔, 称职, 年龄/生日/性别/体重/婚姻/师承, all 4 base stats at their gifted values, health/food/water bars, PK/quest/realm-progress fields) — real, live data, not a stub.quit→ produced the expected graceful "you feel tired and decide to leave" flavor text and closed the session cleanly.
A second, earlier full run (before the stat-menu discovery) with a
different id/name (qinfeng/秦风) reached the same point end-to-end too
(confirmed via the transcript, just without the follow-up look/score
verification since those sends were absorbed by the then-undiscovered
stat menu) — kept as corroborating evidence, not the primary transcript.
A third attempt using a digit-containing id (qinfeng2) correctly
demonstrated check_legal_id's letters-only enforcement rejecting it
repeatedly ("对不起,你的英文名字只能用英文字母。") — confirms the
English-id validation path also works as intended, not just the Chinese-
name path.
debug.log across all 4 connections combined (from a fresh boot):
zero error/denied/crash/segmentation/undefined-type lines —
only the expected compile-time warnings already listed above (unused
locals, unknown #pragma, one unknown escape sequence, one arity-mismatch
warning on convertd.lpc's add(), none fatal).
lpcc sweep
14,643 files (.lpc count after the uppercase-.C rename), well under
the mega-lib bracket but on the larger side (comparable to xiakexing100).
Watched free -h throughout — the lpcc --batch process peaked around
2.7GB RSS with 16GB+ still available system-wide (23GB host), finished in
well under 2 minutes; no concerning memory pressure despite other agents'
concurrent conversions/boots running at the same time.
Result: 14,150 / 14,643 pass (96.63%), 493 failures. Triaged by category rather than fixing every one:
- 366 of 493 (74%) are under personal wizard directories (
u/...) — the standard "test/backup content, not part of the real reachable game world" noise seen on nearly every lib in this project. - 3 more are under
p/residence/...(individual players' personal house instances) — same category, a runtime "Bad argument to set_living_name()" on an NPC clone whoseidis set by a caller that only exists in the real game's create-order, not in an isolatedlpccsingle-object load. - ~10 are the
adm/daemons/network/*intermud/DNS subsystem (missingconfig.h, an old-driver-onlymember_groupefun) — never preloaded, not reachable from registration; same category as the DNS-daemon exclusions documented in §15p/§15ab elsewhere in this project. - 2 are
adm/simul_efun/object.lpc/oo.lpc'sefun::destruct(ob)— a known §6b/§14 "fragment file valid when#included into the realsimul_efun.lpc, invalid whenlpcccompiles it as a standalone top-level object" artifact; confirmed via the real boot's cleansimul_efunload (only one unrelated unused-variable warning there). - The remaining ~110 are a genuine long tail of small, independent, pre-existing content gaps scattered across many different unrelated zones/NPCs/items — no single shared root cause dominates (checked: the largest cluster is 12 files all failing on one missing macro,
inherit S_WEAPON;, for a self-contained "stone monster weapon" NPC subsystem underclone/m_weapon/, never referenced outside that subtree). Smaller clusters:inherit SKI_ZHEN(4 files),inherit DAEMON(4 files), plus a long list of one-off missing headers (uid.h,ANSI.h,valid_kill.h,star.h,priv.h,mailer.h,daemons.h,banned.h,7hai.h,12gong.h) and missing base classes/ inherited files (/u/wiz/super_npc, referenced by a couple ofdeath/zone NPCs). None of these are preloaded, none are reachable from the registration flow, and none share one obvious common fix — treated per AGENTS.md §13 as genuine archive content gaps, not something to fabricate fixes for, consistent with the project's breadth-over-depth policy on a 14k-file lib.
Port
40071, per TODO.md's "next free port" note. Driver launched via
setsid nohup ... & disown from libs/zitengzhan/ (own session, log/
created first); killed by exact PID after testing, never a pattern-based
pkill (other agents had concurrent drivers running for
bmxkx2001, zjdyaryl, and
zjdyzj at the same time on this host).
Re-verification pass: driver rebuild + formatter + WASM (2026-07)
- LPC formatter applied to all
.lpcunderwork/(14,643 total, 14,413 written, 175 unchanged, 55 self-checked errors left untouched as expected on legacy code). - Native re-test against the freshly rebuilt driver (
~/src/fluffos/build-debug/src/driver): clean boot, zero errors of any kind indebug.log(not even the usual benignsocket_bindline). Full registration flow re-verified end-to-end with a fresh id and real Chinese name (ztztest/秦汉试): GB encoding,1→ id flow,new→ English id, admin ("super") password ×2, everyday password ×2, email, gender, gift-allocation menu (9/y), landed in 南城客栈 with the standard 4-NPC welcome;look(room re-render),score(full real character sheet, not a stub),quit(clean "沧茫"-themed farewell text) all correct. - WASM test (
scripts/wasm_client.jsagainstbuild-wasm/src): boots cleanly. Blocked by the documentedquery_ip_number()WASM-mode limitation, textbook example:adm/daemons/band.lpc'sis_strict_banned(site)doesip_now_list = explode(site, ".")then indexesip_now_list[1..3]assuming a real 4-octet dotted-quad; under WASMquery_ip_number()returns an empty string, soexplode("", ".")yields a single-element({ "" })array and the very firstip_now_list[1]access throws*Array index out of bounds., uncaught, andnew_conn_handler()disconnects the connection at the very firstlogon()call — before the encoding/id prompt ever appears. This is exactly the driver-side WASM limitation the task brief anticipated (a site-restriction daemonsscanf/explode-parsing the IP format) — confirmed NOT a mudlib bug (native works perfectly, see above) and NOT patched, per standing instruction. (Superseded — now patched in the 2026-07 WASM-enablement pass below.)
WASM-enablement pass (2026-07): loopback gates + admin seeding
Standard pass per AGENTS.md §1.3b/§1.3e/§1.5:
adm/daemons/band.lpc: newis_local_site(site)helper (loopback / empty / malformed IP ⇒ local) short-circuitsis_banned(),create_char_banned(), andis_strict_banned()— the last one was THE WASM login blocker (itsexplode(site, ".")[j]range parsing throwsArray index out of boundson the empty/garbage WASM IP).adm/daemons/logind.lpclogon():
- logon_cnt > 50 per-IP concurrent-connection cap — loopback exempt.
- the no-query_ip_name destruct and the IP-must-be-digits-and-dots
scan — skipped for loopback/malformed IPs.
- the #ifdef MAX_LOGIN per-IP multi-login cap in get_id() —
loopback exempt.
- The 30s relogin throttle is already commented out upstream; no
uptime()startup gate exists — nothing else to bypass. securityd.lpc'smatch_wiz_site()has NO callers in this lib (dead code) — left unpatched.- Admin seeded:
fluffos/ 浮浮, rank(admin)("总管巫师") viaadm/etc/wizlist. THREE passwords in this lineage for a wizard account, all must differ pairwise where noted: 管理密码 (super/recovery)Admin@2026, 登陆密码 (daily login)Mud@2026, and a 巫师专用密码 (wizard-only password, prompted and set on first wizard login, must differ from the login password)Wiz@2026. Verified: 您的系统权限目前是:总管巫师(admin),update /d/city/kezhan.lpc→ 成功. - Retest: fresh normal registration (
regtest/秦风测) end-to-end into 南城客栈 with look/score (full character sheet)/quit correct; test saves removed. debug.log clean (no new errors).
WASM long-sit boot-watch pass (2026-07)
200s scripts/wasm_boot_watch.sh sit: completely clean, zero grep
hits beyond the known-benign early Unable to open log file:
"log/debug.log" line — no new findings. Proactively fixed
adm/simul_efun/object.lpc's file_owner() (return name → return
dir) as part of a repo-wide port of a bug found live on sibling
zhonghua2 (misattributes 3-level-deep /u/<wiz>/<subdir>/<file>
log_error writes to a bogus path); didn't fire in this lib's own sit,
fixed proactively since it's the identical shared file. Retest: fresh
registration (id sanbztz/秦峰) through look/score/quit, clean.
WASM 修复摘要(迁移自 meta.json 的 group_note)
状态已从过时的 limited 修正——这份档案自己的 README 和 group_note 里从未记录过任何缺陷说明,本轮重新测试也没有发现:完整的管理员登录流程(GB/BIG5 选择→一个"①进入(Enter) ②退出(Exit)"闸门,需要输入字面数字 '1',而不是直接回车→id+登录密码→第三道密码提示,这份档案自己独有的"巫师专用密码",按其自己 README 的说法,和登录密码、管理员/找回密码都不一样)能够干净完成:'您的系统权限目前是:总管巫师(admin)',quit 正常。
深度功能测试(§10.7,本轮):注册之外,全程未发现新 bug
之前几轮已经把这份档案测得非常彻底了(完整注册流程、lpcc 全量扫描、 WASM 长时间静置观察、几乎所有已归档的 bug class 都逐一排查过),但都 停在"注册成功+look/score/quit 正确"这一层,没有真正玩到移动/商店/战 斗/死亡这些更深的系统。这次专门补上这一段。
用之前已注册好的账号(qindeep/秦深)登录,走了:look 确认起始房
间"南城客栈"→ west 到"朱雀大街"→ 再 west 到"董记当铺",list
正确显示"当铺目前没有任何货物可卖"(新档案确实没人当过东西,不是
bug)→ 回到朱雀大街,找到一个游荡 NPC"疥顶小僧",kill 触发了一场
完整的战斗(招式描述丰富多样、命中/闪避判定、血条/精神条正确刷新)
→ 角色被打死,正确触发死亡流程:进入"阴阳界",NPC"朱笔判官 崔珏"自
动(不需要玩家任何操作)念白+翻生死簿+还阳,把角色送到复活点"荒郊
小店",全程没有任何卡死或者需要 present()/环境判断重试的迹象(不是
§7.68 那种多阶段 death_stage() 架构,这个是单一 NPC 自主对话驱动的复
活流程)→ 复活后 score 确认状态合理(属性 20/20/25/25 保持不变,
气血显示"重伤"、综合评价从 1 涨到 90,符合刚经历一场战斗和死亡的预
期)→ 试了留言板,read board/read board 1 都收到"你要读第几张留
言?"提示(这是 notify_fail 提示,不是等待后续输入的交互式问题——
read 1(不带 board)才是正确指令格式),成功读出一条 2011 年的旧留
言,格式正常。
全程 debug.log 保持完全空白,没有发现任何新 bug——这是本项目里
少数几个真正做到"越玩越深、依然找不出问题"的档案,大概率是因为之前
几轮 WASM 修复阶段已经把大部分已知 bug class 都排查并处理过了(见上
文各节),这次的移动/商店/战斗/死亡复活/留言板测试没有找出任何遗漏。
§7.86 跨库扫描修复(留言板 post 崩溃)
BBS_BOARD、BULLETIN_BOARDinherit+ 多余replace_program()致命形状(AGENTS.md §7.86,post命令崩溃):全档案 43 处命中,已删除多余的replace_program(...)调用(保留inherit),逐文件保留原有行尾格式(CRLF/LF 按文件原样)。本次为跨库 §7.86 扫描修复(触发原因:该 bug 已在 6+ 个互不相关的血统家族独立确认,属于近乎普遍的拷贝粘贴模式),仅做编译检查(驱动干净启动、端口正常监听),未做完整 §10.7 深度游玩测试。
深度功能测试第二轮 / Deep functional test round 2 (2026-08-15, post driver-upgrade re-test)
Round-two re-verification against the current native build-debug driver
(post-upgrade — pulls in PRs #1343/#1344 and the corpus-wide %-operator
float-crash fix). Standard checklist + live playthrough-style verification.
Findings:
1. AGENTS.md §7.108 (obj/user.lpc's reconnect() missing
enable_commands()): confirmed active LOGIN_D (adm/daemons/logind.lpc)
calls user->reconnect() on the character body after exec(old_link,
user). Fixed by adding enable_commands(); as the first statement.
Live-verified with two concurrent telnet sessions using this lib's
three-password wizard flow (login password Mud@2026 then
wizard-only password Wiz@2026): session 2 confirmed the "赶出去,
取而代之吗?(y/n)" prompt with y, and the resulting session correctly
dispatched look (real room description) and score (real stat
panel). (An identically-shaped dead obj/user2.lpc — not referenced
anywhere outside the archive's u/ stray-account tree, confirmed via
include/globals.h's USER_OB define pointing only at obj/user —
was left untouched.)
2. AGENTS.md §7.106 (present(file, environment(me)) missing the
environment(me) && guard): this lib ships FOUR separate update.lpc
copies under different cmds/<rank>/ directories. cmds/imm/update.lpc
and cmds/wiz/update.lpc already had the guard; cmds/eld/update.lpc
and cmds/arch/UPDATE.lpc did not — fixed both.
3. adm/simul_efun/file.lpc: log_file() never called assure_file()
before write_file(); added the call (plus a forward declaration).
cat()'s write(read_file(file)) had no null-guard; changed to
write(read_file(file) || "").
4. config.fluffos: maximum evaluation cost was 500000, well
below this project's standard safe value; raised to 5000000.
5. Already correct, no change needed: adm/obj/master.lpc's
log_error() already uses the case-agnostic "arning:" filter
(AGENTS.md §7.10). No adm/daemons/closed.lpc exists, so AGENTS.md
§7.107 does not apply.
Live verification summary: booted the native driver on port 40071 (clean
boot, only pre-existing unused-variable warnings, no fatals;
Initializations complete / Accepting telnet connections both printed).
Logged in as the seeded fluffos admin through this lib's three-password
flow (Mud@2026 then Wiz@2026), confirmed real write access via update
/adm/simul_efun/file (recompiled successfully — the very first update
attempt was silently absorbed by the post-login news pager/gift-menu
sequence, a known pattern on this project; resending it at the settled
prompt worked). Noted the "请选择你想重新设置哪一项天赋" gift-reroll
prompt shown on every wizard login is a genuine pre-existing
d/wiz/init.lpc feature shared across this lineage (confirmed via a
corpus grep hitting several unrelated sibling libs), not a bug — answered
9/y to keep the current values, matching normal play. Ran the
two-session kick-duplicate-login reconnect test described above and
confirmed the §7.108 fix live. No fatal errors in the driver's console
output. Killed the driver by exact PID when done.
本轮修改的文件 / Files modified this round:
libs/zitengzhan/work/obj/user.lpclibs/zitengzhan/work/cmds/eld/update.lpclibs/zitengzhan/work/cmds/arch/UPDATE.lpclibs/zitengzhan/work/adm/simul_efun/file.lpclibs/zitengzhan/config.fluffos
AGENTS.md §7.100 fix (2026-08-19): redundant replace_program(ROOM) landmine
Same corpus-wide bug as the hy5/hymud/... batch-1-6 sweep (ES II
lineage, ROOM macro from include/globals.h). Deleted 2,445 live
standalone replace_program(ROOM); lines from .lpc files under
work/ via fix_710_room.py, plus hand-fixed both room-building
tool copies' string-builder template
(work/clone/misc/roommaker.lpc, work/obj/roommaker.lpc:
str += "\n\tsetup();\n\treplace_program(ROOM);\n}\n"; →
str += "\n\tsetup();\n}\n";). No work/data/ false-negative source
files found. All remaining replace_program(ROOM) matches in work/
are pre-existing //-commented lines (harmless, left alone).
Verified: clean build-debug boot (zero new compile errors, zero
"cannot replace"/"cannot bind" in debug.log), live admin login
(fluffos/Mud@2026/Wiz@2026) + update /d/city/kezhan.lpc +
quit, all successful.
``§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 d/death/npc/bgargoyle.lpc, d/death/npc/wgargoyle.lpc, d/shengdoushi/7hai/nby/npc/haijiangjun.lpc, death/npc/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): 3 accessor(s) in this file returned a raw
never-initialized mapping instance variable (defaults to int 0,
not ([]), until first assigned), crashing any unguarded
keys()/sizeof()/indexing caller for a fresh/untrained character.
Fixed at the accessor level (mapp(x) ? x : ([])) per the documented
remedy. Verified via lpcc --batch static compile check only (not a
live boot) as part of a large mechanical sweep; not individually
functionally re-tested live on this lib.
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() (via the
shared std/char.lpc setup() chain), and 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 recursed back into enable_player() on the same call
stack until "Too deep recursion" aborted the boot on a room's first-ever
visit. Fixed with a true reentrancy flag (in_enable_player_now, set at
entry, cleared before every return), NOT a living()-gated guard --
disable_player() in the same file legitimately re-calls
enable_commands() while already living() (sleep/wakeup via
cmds/std/sleep.lpc, revive via feature/damage.lpc), which a
living() guard would silently break. Verified via lpcc --batch
single-file compile check (PASS). Part of the corpus-wide §7.19 sweep
(Batch C).