Great Tang Journey West: Purple Wisteria Station

✅ 可玩

紫藤站

zitengzhan

🔑 fluffos / Mud@2026 更新 72982d0 2026-09-01 源码 下载 ZIP

▶ 开始游玩 · Play Now

大唐西游之紫藤站(登录后横幅也作【紫藤西游】),属于 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

内容亮点

在线试玩

https://mudlibs.fluffos.info/zitengzhan/

管理员账号 / Admin account

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

本地运行

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.cmaster.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, staticnosave, 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)

Confirmed NOT needed (and how)

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"):

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:

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)

WASM-enablement pass (2026-07): loopback gates + admin seeding

Standard pass per AGENTS.md §1.3b/§1.3e/§1.5:

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

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 namereturn 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 崩溃)

深度功能测试第二轮 / 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:

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