A Chinese Odyssey (Da Hua Xi You)

✅ 可玩

大话西游 (A Chinese Odyssey)

wuhanzhan

🔑 fluffos / Mud@2026 更新 85d6a35 2026-09-02 源码 下载 ZIP

▶ 开始游玩 · Play Now

原始压缩包名为"武汉站",但这只是收藏者标注的站点标签——游戏自身的config 与登录横幅都写明真正的名字是"大话西游",由湖北楚天热线旗下的襄樊分站运营,取材自周星驰同名电影,以搞笑轻松的西游/江湖世界观为底色,融合门派、武功、装备养成等传统 MUD 玩法;属于 ES II(Eastern Stories II)引擎系列,与本项目中的 `wmkj`、`tiexuejianghu` 等同宗但各自独立开发。新人物从"南城客栈"起步,会遇到新手向导、店小二、邮差等 NPC,注册要设置一个独立的"身份识别码"(找回密码用),与日常登陆密码分开;实战经验积累到一定门槛后可以正式投帖拜入"秦王府"这样的真实门派,击杀弱小的平民 NPC 换取钱财购买装备,也是一条可行的收入路线。

English

The original archive was labeled "Wuhan Station," but that's just a collector's site tag — the game's own config and login banner identify its true name as A Chinese Odyssey (Da Hua Xi You), themed after the beloved Stephen Chow film, and run by the Xiangfan branch of Hubei's Chutian Hotline network. It belongs to the ES II (Eastern Stories II) engine family, sharing lineage with this collection's wmkj and tiexuejianghu, though each was developed independently. New characters start at the South City Inn, greeted by a newbie counselor, a waiter, and a courier NPC, and register with a separate recovery PIN kept apart from the login password. A real sect system lets characters petition to join General Qin's Mansion once their combat experience clears a genuine threshold, and killing weak civilian NPCs for coin is a working income path for shop purchases.

README

在线试玩

https://mudlibs.fluffos.info/wuhanzhan/

管理员账号 / Admin account

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

本地运行

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

游戏端口:40052

NOTES · 移植与修复记录

wuhanzhan — 大话西游 (archive title: 武汉站/"Wuhan Station")

Archive: 武汉站.rar. Port: 40052. Status: done (boots clean, full registration flow verified end-to-end with a real Chinese name, lpcc sweep 96.3% pass).

What this is

config.cfg's own name field says 大话西游 ("A Chinese Odyssey" — a play on the famous Stephen Chow *Journey to the West* films), not "武汉站" — the archive's title is just the collector/site label, not the game's own name (noted per the task's request to flag this divergence). The live connection banner confirms the real identity further: "楚天热线 官方游戏站" (Hubei/Chutian-Online official game site) and "热烈庆贺湖北省襄樊大话西游站 的开通" (celebrating the opening of the Hubei Xiangfan/Xiangyang 大话西游 station) — so this is a 大话西游-branded station hosted somewhere in Hubei, archived under a "武汉站" (Wuhan station) label by whoever collected it.

master.lpc's header comment says "for ES II mudlib, original from Lil, rewritten by Annihilator (11/07/94)" — same ES II lineage as es1_win/esI/xkx2001/rzrmud/xo/bmxkx2001/kxkj/ yueyingqiyuan (archives #12/13/25/23/28/45/53/54), sharing the adm/obj/{master,simul_efun} + adm/simul_efun/ + adm/daemons/ + cmds/{std,wiz,usr,adm,...}/ + std/ + obj/ + d/<zone>/ layout shape. It is NOT byte-identical or even structurally close to any single prior ES II lib checked (confirmed via diff against mhxyqd's/mhxy's master.c despite some files elsewhere in the tree carrying a "神话世界· 西游记·版本4.50" header comment that superficially suggested that different, unrelated 西游记/xiyouji.org lineage — another confirmation that header-comment branding text can be misleading and copy-pasted across completely different codebases; always verify via structural/diff comparison, not comment text alone).

Root: raw/武汉站/武汉站/ (nested one level, doubled directory name). config.cfg: master file : /adm/obj/master, simulated efun file : /adm/obj/simul_efun. Raw archive: ~9,898 real LPC files (after excluding the bundled driver source below) plus a large binaries/ (~10,719 .b compiled-binary dumps from the original archive's "save binaries directory" feature — harmless leftover data, never used by this driver since LPC->C compile-to-binary is never enabled here) and backup//bin/ scaffolding.

Pre-processing: bundled MudOS v22pre11 driver source excluded

raw/.../bin/v22pre11/ is a full bundled MudOS v22pre11 driver C source tree (src/interpret.c, array.c, backend.c, etc. — real driver internals, not mudlib LPC) sitting alongside the mudlib content, the same "archive bundles a driver too" situation seen on xyj2000f and others. Renamed to bin/v22pre11.orphaned-driver-src/ in raw/ before running convert_lib.sh, per precedent, so it wouldn't be mistaken for real mudlib content. Note: this rename does NOT stop convert_lib.sh's blind tree-wide .c.lpc rename from still touching the ~105 .c files inside it (the rename walks by extension, not by directory name) — this is expected/harmless per AGENTS.md ("driver runs mixed-extension trees fine") since nothing in the real mudlib ever inherits/load_object()s anything under that path; it does, however, show up as a chunk of the lpcc sweep's failure count (missing driver-only headers like uid.h/spec.h) — noted below under the sweep triage, not something to fix.

Fixes applied

1. AGENTS.md §15h (the universal GBK-byte-range bug), two sites: - adm/simul_efun/chinese.lpc's is_chinese(): GBK lead-byte range check (str[0] > 160 && str[0] < 255, strlen(str)>=2) replaced with a CJK Unified Ideographs codepoint range check (str[0] >= 0x4e00 && str[0] <= 0x9fff, strlen(str)>=1). - adm/daemons/logind.lpc's check_legal_name(): byte-count bound strlen(name) < 2 || > 12 → character-count bound < 1 || > 6 (matching the message's own stated "1 到 6 个中文字"); removed the i%2==0 && even-byte-offset gate in the per-character is_chinese sliding check so every character position is actually checked (not just alternating ones, which was the GBK-lead-byte-parity leftover). 2. AGENTS.md §15p: /adm/daemons/network/dns_master was present in adm/etc/preload — removed proactively before the first boot attempt. Booted clean well under 15 seconds on every attempt, no hang. 3. AGENTS.md §14 (valid_override 2-arg → 3-arg): master.lpc's valid_override(file, name) only matched file == SIMUL_EFUN_OB, which never equals the physical file for an efun:: call written inside an #include-composed fragment (adm/simul_efun/object.lpc's efun::destruct(ob) and efun::living(ob) are concrete examples of exactly this shape in this lib). Upgraded to varargs int valid_override(string file, string name, string main_file), accepting either file or main_file matching SIMUL_EFUN_OB/MASTER_OB. 4. AGENTS.md §15s (new confirmation, same bug class as yueyingqiyuan): adm/simul_efun/message.lpc's varargs void tell_room(mixed ob, string str, object *exclude) left exclude as a bare int 0 when called with only 2 args, which this driver's message() efun (4th arg typed void|object|object*) rejects. Fixed with exclude || ({}) before delegating to the real efun. (Only 4 call sites of tell_room exist in this lib specifically, vs. yueyingqiyuan's 578 — fixed regardless since it's a one-line, zero-risk shared-root fix.) 5. AGENTS.md §8h (convertd.lpc Greek-table stray-backslash typo), 45 occurrences (the largest count seen for this bug so far — prior libs had 1-2): adm/daemons/convertd.lpc's GB/BIG5 charset-table lines of the shape "<greek-letter><PUA-codepoint>\", (trailing backslash escaping the closing quote, merging that line into the next) — found one manually via the first lpcc compile of simul_efun, then wrote a byte-level Python regex sweep (rb'^("[^"]*)\\("...)(\r?\n)$' shape) to find and fix all 45 in one pass; verified 0 remain afterward. (The "PUA codepoint" half of each table entry, e.g. U+E5A1, is a genuine, intentional artifact of the original GBK code page's user-defined region carried through by iconv — not something to "fix", only the stray backslash was ever a bug.) 6. NEW bug class, proposed as AGENTS.md §15u: master.lpc's log_error() apply (the master apply for compile-time diagnostics) showed the scary "default error message" config text (你发现事情不大对了,但是又说不上来。) to every connected non-wizard player for every compile-time WARNING, not just fatal errors — this driver's compiler funnels ALL diagnostics through APPLY_LOG_ERROR/master::log_error() with no severity distinction at all (confirmed by reading ~/src/fluffos/src/compiler/internal/ compiler_utils.cc's smart_log()/report_compile_diagnostic(), both unconditionally calling safe_apply_master_ob(APPLY_LOG_ERROR, 2) regardless of whether the diagnostic is a warning or an error). Since this driver is measurably stricter/noisier than the original MudOS target this lib shipped against (this port's own staticnosave rename alone produces an "Illegal to declare nosave function" warning on every such function; #pragma optimize/similar produce "Unknown #pragma, ignored" warnings), and since most of a mudlib's object tree is only *lazily* compiled the first time a connected player's actions happen to touch it, an ordinary short registration+look session triggered 98 of these messages to a ~90-second-old, otherwise perfectly healthy connection — purely from harmless "unused local variable"/"illegal nosave" warnings on rooms/NPCs/items being compiled for the first time as the player walked in. Fixed: the non-wizard branch of log_error() now only shows the message when message does not contain the substring "warning:" (i.e., only for a genuine compile error); wizards still see the full diagnostic text as before, unchanged. Diagnosis path (kept here since it was non-obvious and the eventual root cause was two apply-mechanisms away from the visible symptom): (a) confirmed error_handler() — the OTHER, similarly-shaped master apply — was NOT the source, by temporarily instrumenting it with an unconditional efun::write_file()+efun::write() marker and observing it fire correctly for one genuine runtime error (see next item) but ZERO times during the 98-message spam; (b) found /log/log (already being written unconditionally by log_error()'s own write_file() call) contained the exact matching compile-warning text at matching volume across the session, pointing straight at log_error() instead. 7. Bonus fix found via the same diagnosis (not a new bug class, but a real, permanent improvement): master.lpc's error_handler() carried a stale comment ("whatever we return goes to the debug.log") reflecting old-MudOS behavior that does not hold on this driver — per ~/src/fluffos/docs/apply/master/error_handler.md, the real apply signature is void error_handler(...); the return value is simply discarded, so a genuine uncaught runtime error was previously visible ONLY as an ephemeral tell_object() to whichever player happened to be connected at the moment (nothing durable at all if that player was a non-wizard, or if nobody was connected). Added a permanent efun::write_file("/log/RUNTIME_ERRORS", trace) at the top of error_handler() so real runtime errors are never silently lost again. 8. New pre-existing content bug, 19 files (found via the lpcc sweep, not registration-path-blocking but a real, cheap, mechanical fix): several clone/misc/*cloth*, clone/weapon/{gangdao,huolong-bian}, clone/npc/obj/wizardsword, and wizu/focus/songhe-sword item files use ANSI-color macros (HIW"..."NOR, MAG "..." NOR, etc.) but never #include <ansi.h> themselves (only <weapon.h>/<armor.h>, which don't pull it in — LPC #defines are per-compilation-unit, not propagated by inherit). Without the macros defined, HIW/NOR/etc. are bare undefined identifiers sitting directly against a string literal, which is a hard parse error ("syntax error, unexpected L_STRING") — this is a genuine pre-existing authoring bug (these items likely never worked even on the original server), not something the conversion introduced. Fixed by inserting #include <ansi.h> as the first #include/inherit line in all 19 files; re-verified each via lpcc individually (zero errors after) and via the full sweep (pass count rose from 9509→9528, exactly matching all 19 files plus one halfgod.lpc cascade fixed transitively through wizardsword.lpc).

Confirmed NOT needed (checked by reading the actual source)

Interactive test result — full registration flow

Read adm/daemons/logind.lpc's full input_to callback chain (logonencodingget_idconfirm_idget_nameget_super_passwordconfirm_super_passwordnew_passwordconfirm_passwordget_emailget_genderconfirm_giftenter_world) before scripting the test. No hidden pre-id prompts of any kind — the GB/BIG5 encoding question is fully commented out (encode=0 hardcoded), so the very first live prompt is the real English-id prompt.

Ran the complete flow in ONE continuous mudclient.py connection, twice independently after all fixes (once immediately confirming the 你发现 事情不大对了 spam bug before the log_error() fix, once cleanly after):

--send "qinfengx" --send "y" --send "秦风终" \
--send "abcd12345" --send "abcd12345" \
--send "xyz987654" --send "xyz987654" \
--send "[email protected]" --send "f" --send "y" \
--send "look" --send "quit"

Result: id accepted → confirmed as new → real Chinese name "秦风终" accepted on the first try (no rejection, confirming the §15h fix) → 身份识别码 (recovery PIN) set+confirmed → 游戏登陆密码 (login password) set +confirmed → email accepted → gender chosen → gift/stat-roll accepted → dropped into the actual starting room, 南城客栈 ("South City Inn"), with fully correct Chinese room description, exit list, board, and 3 NPCs listed (武汉站新人辅导员/Hui zhang, 店小二/Xiao er, 邮差 千里眼/Qianli yan). look correctly re-displayed the room. Zero spurious messages of any kind during this final run (confirmed by grepping the transcript for the previously-spamming default-error-message string: 0 hits). quit disconnected the socket. debug.log for this session shows nothing beyond warnings and the one known/accepted emoted.o restore error above — no fatal errors, no crashes.

Registration was ALSO independently verified with ids qinfeng/秦风, qftest/秦风三, and qinfengw/秦风五 across earlier boots during development of the fixes above — all reached the same starting room successfully.

lpcc sweep

9,898 files (.lpc, includes the harmless orphaned bundled-driver-source files under bin/v22pre11.orphaned-driver-src/). 9,528 pass / 370 fail (96.3%), up from 9,509/96.06% before the 19-file ansi.h fix above. Memory stayed completely healthy throughout both sweep runs (18-19GB available out of 23GB at all times, well clear of AGENTS.md §6b's warning threshold).

Failure categories triaged by grouping error text (not fixed individually, per §6b/§13 — none of these affect boot or registration):

Port / process

Port 40052 (next free after 40051 per TODO.md at the time this archive was picked up). Driver run via cd libs/wuhanzhan && ~/src/fluffos/ build-debug/src/driver config.fluffos, noninteractive stderr write : 1 added to config.fluffos (harmless, generically useful for future debugging on this lib — lets efun::write() reach stderr for non-interactive/preload-time diagnostics). Driver process was killed by exact PID after all testing; confirmed nothing left listening on 40052.

Process-management note for whoever reviews this: during troubleshooting, one pkill -f "build-debug/src/driver config.fluffos" call (a broad pattern match, before switching to exact-PID kills for the rest of this session) killed every driver process on the host matching that command line — since every lib in this project launches its driver with the identical relative config.fluffos argument, this almost certainly also killed other agents' concurrently-running driver processes (observed nitan_ceshi/nitan_san-named driver processes running at the time, likely collateral). Worth flagging to any concurrently-running agent/session in case their driver died unexpectedly around 16:07 local time during this session.

Re-verification pass (driver rebuild + LPC formatter + WASM build)

WASM-enablement pass (loopback-allow / admin seeding)

Gates found and patched (AGENTS.md §1.3b/§1.3e/§1.5):

Retrofitted fail-open → fail-closed (2026-07-24): is_local_addr() and the logind.lpc encoding() address-gate condition were both originally written so that ANY malformed/non-string ip was treated as local — defensive against the old WASM driver bug where query_ip_number() returned garbage for every WASM connection. That driver bug is now fixed (WASM reports a clean "127.0.0.1" same as native), so there is no remaining justification for "can't parse it" ⇒ "must be loopback". Changed: - band.lpc is_local_addr(): !stringp(site) || site=="127.0.0.1" || strsrch(site,"127.")==0 || sscanf(site,"%*d.%*d.%*d.%*d")!=4stringp(site) && (site=="127.0.0.1" || strsrch(site,"127.")==0). Its three callers (is_banned/create_char_banned/ is_strict_banned) each got an added if (!stringp(site)) return 1; right after the is_local_addr() check, so a non-string site is now treated as banned (deny) instead of falling into the regexp() match on a non-string array element. - logind.lpc encoding(): the condition deciding whether the whole non-loopback address-gate block runs was !stringp(ip_number) || ip_number=="127.0.0.1" || strsrch(ip_number,"127.")==0 || sscanf(ip_number,"%*d.%*d.%*d.%*d")!=4 (true ⇒ skip every gate below AND coerce ip_number to "127.0.0.1"). Replaced with a new is_local local computed as stringp(ip_number) && (ip_number=="127.0.0.1" || strsrch(ip_number,"127.")==0); only that now skips the gate block. An unparseable/non-string ip_number falls into the else-branch and goes through the real ban/strict-ban/name checks and the digit-validation loop, same as the pre-WASM original code (which would legitimately destruct() a connection whose reported ip isn't a clean dotted-quad — no longer expected to trigger for WASM now that the driver bug is fixed).

Admin account: id fluffos, login password Mud@2026, recovery PIN (身份识别码) Pin@2026a, name 浮浮, granted (admin) (top rank) via adm/etc/wizlist. Verified: login shows 权限:(admin), update /adm/daemons/band → 成功.

Save files for the orchestrator to force-add (untracked, not gitignored):

Retest: fresh normal registration (qinshier / 秦十二) lands in 南城客栈, look/score/quit correct, zero 执行时段错误 in transcript and debug.log. Test char removed (fluffos kept).

Re-retest after the fail-closed retrofit (2026-07-24): fresh boot, fresh registration (id qretest, real Chinese name 秦十三, PIN Pin@2026a) through look/score/quit — landed in 南城客栈, score output correct, clean quit. fluffos/Mud@2026 admin login re-verified: look then update /adm/daemons/band成功! (cosmetic compile-warning spam shown to the player as usual, no actual error), then clean quit. debug.log had exactly the one pre-existing, already-documented boot-time emoted.o restore error (caught by master.lpc's preload(), harmless — see the §15m entry above) and zero NEW runtime errors. Test char qretest removed afterward; fluffos kept.

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

First real *playthrough* pass on this lib (all prior passes verified only registration + look/score/quit + admin login, or watched boot output). Read doc/help/newbie first (a generic, shared-template "西游记 新手指南" — branding mismatch from "大话西游" but its content matches this lib's actual starting room/mechanics exactly: 南城客栈, fight vs kill, learn/skills/practice, apprentice/bai, hp bars, buy ... from ...), then played native (build-debug) via scripts/mudclient.py. Two real, live, first-ever-visit "Too deep recursion" crash bugs were found and fixed — this pass is the third lib (after bxsj §7.16 and xiyouji §7.17) where a genuine playthrough caught something no earlier boot/registration pass ever could.

Test character: id qiufeng, Chinese name 秋风然, password Passwd2026kept as the representative playthrough character (save files work/data/user/q/qiufeng.o, work/data/login/q/qiufeng.o). State: 潜能 105499, 道行 一百十年, skills dodge/force/literate/parry/ spells all 60 (from the newbie-gift NPC) plus unarmed 60/32 (from the organic teacher-NPC learn path below); inventory: one 粗布衣 (default linen robe, re-granted on full login per the quit-drop mechanic already documented above). A second, throwaway id linfeng/秦风起 was used during the bug-hunting phase and its save files were deleted afterward (not kept — messier state, no incremental evidence value once qiufeng existed).

Bug 1 (existing class, §6.1) — absolute-path angle-bracket include silently voided a book item, first found via a room-first-visit crash

d/obj/books-nonskill/book-qujing.lpc:7: #include </d/qujing/obstacle.h> — an ABSOLUTE path inside angle brackets, which per AGENTS.md §6.1 never resolves on this driver (the <> resolver doesn't special-case absolute names). This is the exact, already-cataloged bug class — just not previously found in this lib, because the earlier pass's grep for it (#include <[A-Z], see this file's own "Confirmed NOT needed" §15t entry above) was case-sensitive and only matched absolute paths starting with an uppercase letter; this one starts lowercase (/d/...) and slid through. Whole-lib rescan with grep -rn '#include <\/' --include='*.lpc' found exactly this one occurrence lib-wide.

Bug 2 (NEW bug class, draft below) — calling enable_player()/enable_commands() from init() (not create()) makes a room's first-ever visit call the room's own NPC's init() recursively until "Too deep recursion"

Found while walking qiufeng/linfeng from 南城客栈 to 将军府 (General Qin's mansion, d/jjf/) to test the sect/apprentice mechanism — the room d/jjf/front_yard2 (练武场, home to the sect's 掌门大师兄 identity-succession NPC, d/jjf/npc/zhangmen.lpc) crashed with the same scary default-error message on its first-ever visit, every single time, reproduced across five independent fresh-boot attempts before being root-caused and fixed. Player-visible symptom matched §7.17's own prior description almost exactly: the room is still entered, but the NPC's title renders with a stray leading "0" (0掌门大师兄 大弟子) — corrupted state from the crash, not a content typo.

Diagnosis (the two false leads, then the real one — kept here since neither is obvious from reading the code cold): 1. First suspected §7.17's own two documented shapes verbatim (std/room.lpc's setup()reset() reentrancy, and zhangmen.lpc's create_identity() self-locating its room via a forced call_other(path,"???"); find_object(path) instead of environment(this_object()), exactly the §7.17 "belt-and-suspenders" anti-pattern). Fixed BOTH proactively (a nosave int resetting_now; reentrancy guard added to std/room.lpc reset(); create_identity() changed to prefer environment(this_object()) when it already matches the hardcoded where path). Both are real, safe, permanent improvements — kept — but neither one stopped the crash: debug.log still showed Too deep recursion on the exact same two lines (zhangmen.lpc:39/:626) after both fixes, on a genuinely fresh boot. 2. debug.log's terse Too deep recursion lines (unlike a normal LPC error, this class doesn't get a full call-argument trace in RUNTIME_ERRORS — the interpreter can't safely unwind through master::error_handler()'s own apply when the stack is already exhausted) gave only single stack frames, not a call chain. Per AGENTS.md §10.3, switched instrumentation from write_file() to efun::write() (write_file's own ACL check needs stack headroom too, and was silently swallowing the diagnostic lines closest to the actual crash). With that, the real shape became visible: the SAME zhangmen#66 object's init() was being called over 40 times in a row, each nested call starting before the previous one returned, every single time reporting caller=/obj/user#1 (the connecting player) — a real reentrant self-loop, not a room re-populating twice. 3. zhangmen.lpc's init() ends with an unconditional me->setup(); (also reachable via me->reset_me(me)'s own internal setup() call a few lines earlier) — redundant, since create() already called setup() once before the object was ever moved into a room. std/char.lpc's setup() calls enable_player() (feature/command.lpc), which calls the real efun enable_commands() with no explicit argument. Per this driver's own docs (~/src/fluffos/docs/efun/interactive/enable_commands.md, BUGS section): *"Do not call this function in any other place than create() or strange things will likely occur"* — calling it with a truthy/default setup_actions re-runs init() on the object's environment, siblings, AND own inventory as a side effect. Calling enable_player() a SECOND time on an object that's already living (i.e., from init(), not create()) makes the driver call that object's own init() again while the current init() call is still on the stack — genuinely reentrant, repeating until the driver's call-depth limit aborts.

Fix (feature/command.lpc's enable_player(), the single shared entry point every setup() call in this lib routes through): added if (living(this_object())) return; as the very first line. living() reliably reflects whether enable_commands() has already run for this object, and disable_player()'s own path (disable_commands() immediately followed by enable_commands() again) still works correctly since it calls the real efun directly, not through enable_player() — so the guard is inert for every legitimate call site (the original create()-time call, or any post-disable_player() re-enable) and only blocks the pathological repeat-call case. Verified: 5 independent fresh-boot reproductions before the fix (100% reproduction rate, debug.log showing Too deep recursion at securityd.lpc:178×1-6 then zhangmen.lpc:39/:626×2 every time); zero reproductions in 3 independent fresh-boot retests after (RUNTIME_ERRORS/debug.log both clean except the one pre-existing, already-documented emoted.o preload error). The stray "0" in the NPC's title is also gone post-fix (将军府掌门大师兄 大弟子, correct).

Scope: d/jjf/npc/zhangmen.lpc was the only one live-reproduced (it's what a fresh character's most direct sect-mansion route reaches first), but the identical create_identity()/init() shape — including the same redundant me->setup(); — is copy-pasted into every other sect's "掌门/大师兄" identity-succession NPC in this lib: confirmed byte-identical via script across 27 more files (d/nanhai,xueshan,tianwang,sea,moon,lingtai,death,shaolin,mojiao, shushan,qujing/{wudidong×2,wuzhuang,pansi×3},lingjian×2/npc/zhangmen*, plus d/npc/zhangmen.lpc — dead/unreachable, no room ever places it — d/zhangmen.lpc, and personal-wizard-dir copies under u/{moonr/sadboy,qlwy,sadboy}/.../zhangmen*.lpc, wizu/focus/zhangmen.lpc). Applied the SAME create_identity() environment()-preference patch to all 27 by exact byte-for-byte block match (verified via lpcc --batch, all 28 total PASS) — proactive fix by code-shape match, not individually live-reproduced, per the xiyouji §7.17 precedent ("only one was live-reproduced, the rest fixed proactively"). The root enable_player() guard in feature/command.lpc protects ALL of these (and anything else in the lib with the same misuse shape) uniformly, since it's the single shared choke point every setup() call routes through — this is why the fix was placed there rather than patched per-file.

Cross-lib risk flagged, not chased further (out of this task's scope): wuhanzhan is documented in AGENTS.md §11 as a 大话西游 sibling of the 西游记/xiyouji.org ES II branch, and this exact "神话世界·西游记·版本4.50"-headered create_identity()/init() template (header comment only — NOT proof of shared codebase, per this file's own earlier caution) appears, per grep -rl "神话世界" libs/*/work --include='*.lpc', in 60 different libs in this corpus. Any of them that also calls setup()/enable_player() from init() (not just create()) is a candidate for the same crash on first room visit — worth a targeted grep (grep -rln 'me->setup();' <lib>/work --include='*.lpc' cross-referenced against which of those hits are inside a void init() function) on a future pass through any sibling, rather than assuming this fix's scope stops at wuhanzhan.

What else was tested and confirmed working

Draft new AGENTS.md bug-class entry (for the orchestrating session)

Proposed as a new §7.18 (or a sibling case folded into §7.17's own "first-ever-visit reentrancy" family, orchestrator's call), title: "Calling a create()-only driver primitive (enable_commands()/ enable_player()) from init() reentrantly re-triggers that same object's own init(), crashing a room's first-ever visit."

Body draft: This driver's own docs (efun/interactive/enable_commands.md, BUGS section) state plainly: *"Do not call this function in any other place than create() or strange things will likely occur."* enable_commands() (with no explicit setup_actions argument, or any truthy one) re-runs init() on the calling object's environment, siblings, AND inventory as a documented side effect — a normal, intentional mechanism for "an object just became live/interactive, re-announce it to everyone nearby." If a mudlib's shared setup()/enable_player() wrapper (a common pattern: create() calls setup() once to configure a fresh object, and some OTHER code path — an NPC's own init(), a reset_me()/respawn helper, a "wake up"/"revive" handler — ALSO calls the same setup() later, expecting it to just be idempotent stat recalculation) is reachable from init() itself, the second enable_commands() call re-triggers init() on the SAME object while the first init() call is still on the stack — genuinely reentrant, not a one-shot double-call, because each new init() invocation runs the same setup() line again, compounding until the driver's call-depth limit aborts with Too deep recursion. First found+fixed in wuhanzhan's d/jjf/npc/zhangmen.lpc (and 27 structurally-identical sibling sect NPCs, all sharing a "神话世界·西游记" template) — its init() calls me->setup() (both directly and via reset_me()), redundant with create()'s own earlier setup() call, and that redundancy is what triggers the cascade. Symptom shape mirrors §7.17 closely (first-ever room visit only, invisible once objects are warm in memory, corrupted NPC state showing as a stray literal "0" in a title/name field) but the mechanism is a different, more general driver-API-misuse class — not a room-reset double-fire, and not a hardcoded self-locate path — so it can recur in ANY lib whose setup()-style wrapper calls enable_commands()/enable_player() and is reachable from anywhere other than create(), independent of lineage. Detection: grep for enable_commands( / enable_player( call sites, then check whether each one's enclosing function is reachable from init() (directly, or transitively through a same-purpose "recover"/"reset_me"/"revive" helper) rather than only from create(). Fix: guard the enable_player()-equivalent wrapper itself with if (living(this_object())) return; at its top (inert for every legitimate call site — first-time create(), or any real post-disable-re-enable — since living() tracks exactly whether enable_commands() already succeeded for this object) — a single, low-risk, shared choke-point fix rather than patching every individual redundant setup() call site.

Process hygiene note for this pass

The native driver for this lib was restarted many times during the bug-2 diagnosis (each fresh-boot repro needed a truly first-ever front_yard2 visit); every restart used the exact recorded PID for the kill, ss -tlnp-verified before starting the next one. One collision found mid-pass: a driver already listening on port 40052 at session start turned out to be a DIFFERENT lib's process (rzrmud, confirmed via readlink /proc/<pid>/cwd) that happened to be bound to this port from a stale prior run — not killed, left alone, this lib's own driver was simply started on its correct configured port after confirming the mismatch. Temporary efun::write()/write_file() diagnostic lines added to d/jjf/npc/zhangmen.lpc and std/room.lpc during the bug-2 diagnosis were fully removed before the final commit-ready state (only the two real structural fixes remain in those files); no DBGTRACE.log or other scratch file was left behind. Runtime side-effect files touched incidentally by testing (u/npc/log, u/sadboy/log — per-wizard-dir compile-warning logs; data/zhangmen/zhangmen_qin_qiong.o — an NPC's own save data) were reverted to their pre-session committed state, keeping the diff limited to the intentional source fixes plus this NOTES.md update plus the qiufeng test-character save files.

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

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

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

1. config.fluffosmaximum evaluation cost1400000 提升 到 5000000(本项目标准安全值,预防性调整,此前的值本身未被确 认在风险区间内)。 2. adm/simul_efun/file.lpclog_file() 没有 assure_file() 目录预建保护,补上调用及前向声明;cat() 补上 read_file() || "" 空值防护。 3. obj/user.lpc::reconnect()(AGENTS.md §7.108,第九条独立确认 的血统)adm/daemons/logind.lpc 有同款 exec(old_link, user); 踢掉重复登录写法,reconnect() 缺少 enable_commands()。按 §7.108 记录的写法预防性修复,现场用两个 真实连线复现"保持第一个连线不断开→第二个连线登录→答 y 踢掉旧连 线"验证:score 修复后立即正常显示完整角色档案。

复核确认非 bugupdate/score 等指令首次惰性编译时,管理员 账号屏幕上出现"编译时段错误:...Warning: Unused local variable"这 类原始编译诊断文字——这是 master.lpc::log_error()(AGENTS.md §15u,本档案自己第一轮已修复并留有详细注释)的既有设计:巫师 账号(wizardp(this_player(1)))本来就应该看到完整原始编译诊断, 只有非巫师普通玩家才走过滤警告的分支。本轮误以为是泄漏又重新核对 了一遍代码,确认这是正确行为,未做任何改动。

cmds/wiz/update.lpc(§7.106)已经是正确写法;本档案无 adm/daemons/closed.lpc,不受 §7.107 影响。

现场验证摘要

驱动干净启动,管理员 fluffos/Mud@2026 登录确认 您已经成功登陆游戏!目前权限:(admin)update /adm/daemons/ logind 成功验证真实写入权限。踢掉重复登录重连路径现场验证通过 (见上)。debug.log 全程干净(710 行,无真实错误)。

本轮修改的文件

§7.100 sweep (2026-08-19)

Fixed the corpus-wide inherit ROOM; ... replace_program(ROOM); redundant-replace bug (AGENTS.md §7.100). 375 live occurrences deleted: 368 via scripted sweep (fix_710_room.py), plus 7 hand-fixed roommaker-tool templates across 5 separate tool copies (clone/misc/roommaker.lpc, obj/roommaker.lpc, d/obj/clone/misc/roommaker.lpc, u/ken/obj/roommaker.lpc — simple variant, 1 each; wizu/link/obj/roommaker.lpc — "room_code"/str 3-occurrence variant). 10 already-commented-out instances (incl. 2 in wizu/link/obj/citymaker.lpc) left untouched. One occurrence NOT fixed: d/wiz/officer/officer.lpc has a non-standalone setup() replace_program(ROOM) with no semicolons anywhere in its create() — this file is already syntactically broken independent of this bug (inherit ROOM void create() { is missing its own semicolon), so it never compiles regardless; left alone as out of scope for this sweep. No real .lpc source found under work/data/. Verified via build-debug driver boot: clean compile (only pre-existing pragma/unused-variable warnings), port 40052 listening, zero new "cannot replace"/"cannot bind" debug.log lines.

深度功能测试第三轮 / Deep functional test round three (2026-08-20) — resolved all 3 flagged gaps

Followed up on the three items explicitly flagged as unresolved at the end of the round-two pass above (sect-join, shop purchase, death/respawn), using the kept representative character qiufeng/秋风然 plus admin (fluffos) goto/summon/clone/call to move faster, while letting every actual game-mechanic transition (combat resolution, purchase, death, respawn) run through real, unmodified game logic. Native build-debug driver, port 40052, debug.log/RUNTIME_ERRORS watched throughout.

1. Sect-join — RESOLVED, works correctly. qiufeng's combat_exp was already 110000 (above d/jjf/npc/qinqiong.lpc's 100000 floor, grown from the round-two combat testing) — the "far under the floor" gap from round two was a stale snapshot, not a live retest. apprentice qin (id is qin, not qinqiong — the two-word "qin qiong" alias needs present()'s single-token match) correctly triggered attempt_apprentice()command("recruit ..."); a second apprentice qin completed the join for real: "你恭恭敬敬地向秦琼磕了四个响头,叫道:「师父!」" + "恭喜你成为将军府第三代弟子" — score confirmed title changed to 将军府第三代弟子, 师父 秦琼, family dbase entry populated (master_id, family_name, generation:3). No admin shortcut was used for the join itself, only to reach qinqiong's room (d/jjf/keting) via goto+summon.

2. Shop purchase — RESOLVED, real combat-loot income path confirmed working end-to-end. Traced the actual currency system: cmds/std/buy.lpc's can_afford()/pay_money() (feature/finance.lpc) check real coin/silver/gold/cash inventory objects, NOT the balance dbase field used by feature/vendor_sale.lpc's bid/deposit shop-ownership-auction subsystem (a red herring — F_VENDOR_SALE's buy_object()/complete_trade() route purchase payment through finance.lpc, not balance). The feature/bankowner.lpc/bankpawnowner.lpc pawnshop/bank system suggested by the round-two note is dead code — confirmed via grep -rn F_BANKOWNER\|F_BANKPAWNOWNER returning zero hits anywhere in the lib; not a viable income path. Real path: killed d/city/npc/liumang.lpc (小流氓, a weak civilian NPC with add_money("coin", 40+random(90)) in its own create()) via the real kill command (admin cloned fresh instances into a fightable room, d/city/beiyin4, since the original wandered off via its own random_move chat behavior before it could be engaged — 南城客栈 itself has no_fight:1, confirmed real design, so combat has to happen elsewhere). cmds/std/kill.lpc's do_kill() has the NPC reciprocally call kill_ob(me) on the player whenever obj->accept_kill(me) is undefined (true for liumang, which doesn't override it) — genuine two-way lethal combat, not scripted. Looted get all from corpse for 56 then another 98 coins (154 total, two liumang kills); returned to 南城客栈 via admin summon, list showed 桂花酒袋(jiudai) priced at 一两银子 (100 raw currency units), buy jiudai from xiao er succeeded: "你向店小二买下一个桂花酒袋" — inventory confirmed 154→54 coins deducted (exactly 100) and the jiudai item received. (The purchased item and remaining coins were later dropped on a subsequent unrelated quit, per this lib's own already-documented "only worn/equipped items persist via autoload" mechanic — not a bug, the live-transcript proof of the successful purchase/deduction was already captured before that.)

3. Death/respawn — RESOLVED, full real death→limbo→revival cycle confirmed working, zero debug.log/RUNTIME_ERRORS regressions. qiufeng's huge combat_exp (110000) and full vitals made this untestable against any actually-reachable early NPC in reasonable time, so used admin call qiufeng->set(...) to temporarily reduce combat_exp to 200 and kee/gin/sen/eff_kee/eff_gin/eff_sen to 5 each (restored afterward — see below) — purely to get a real fight to a lethal conclusion quickly; the death transition itself ran entirely through unmodified game logic. kill liumang (freshly cloned) produced real mutual damage; feature/damage.lpc's receive_wound() (gated on me->is_killing(victim), true for both sides here since kill triggers reciprocal kill_ob()) pushed eff_kee negative, triggering the real "你的眼前一黑...你死了。" → die() → move to DEATH_ROOM (/d/death/gate, "阴阳界") → the exact d/death/npc/pang.lpc death_stage() reentrancy-guarded NPC already verified clean for §7.112 in this same pass's standard-checklist section above — confirmed live: judge NPC 崔珏 (Cui Jue) dialogue fired correctly, death_stage() progressed through its message sequence without any reentrancy/"Too deep recursion" symptom, then reincarnate() correctly restored eff_kee/eff_gin/eff_sen to max_kee/max_gin/max_sen and moved the player to REVIVE_ROOM (荒郊小庙). score post-revival showed the expected death penalty applied (道行/combat rank reduced, per the death dbase mapping's own skill_loss/combat_exp_loss fields — game design, not a bug) and full HP bars (matching the restored eff_* caps). Sect membership (family dbase entry) survived death correctly. combat_exp restored to its real pre-test value (110000) via admin call+save() afterward so the kept representative character's progression isn't left corrupted by the test scaffolding.

Bonus fix found via RUNTIME_ERRORS while watching the above (not part of the 3 flagged gaps, found for free): master.lpc's log_error() (the same apply already touched by §15u) unconditionally does efun::write_file(home + "log", message) to log every compile diagnostic to the offending file's owner's home directory — but home = user_path(file_owner(file)) doesn't always point at a real, existing top-level /u/<name>/ directory: files under a *nested* wizard subdirectory (e.g. /u/sadboy/pansi/npc/zhangmen.lpc, one of the 27 §7.17 create_identity() sibling fixes from the round-one pass) can carry a euid ("pansi") that only exists as a subdirectory name, not a real home (there is no /u/pansi/ — only /u/sadboy/pansi/). This threw an uncaught *Wrong permissions for opening file /u/pansi/log for append runtime error on every boot (confirmed reproducible: appeared in a clean RUNTIME_ERRORS on the pre-fix boot for /u/sadboy/pansi/npc/zhangmen.lpc's own benign "Unused local variable" warnings, gone entirely on a fresh post-fix boot). Fixed by wrapping the write in catch() — this is a best-effort diagnostic log write, not critical state, so silently swallowing a bad/nonexistent per-wizard home directory is safe and matches this function's already-established defensive posture from the §15u fix. Verified: fresh boot after the fix shows only the one pre-existing, already-documented emoted.o restore error in RUNTIME_ERRORS, zero pansi entries; registration/login/sect-join/combat/purchase/death/respawn all re-verified working post-fix via a fresh qiufeng login.

Standard checklist re-confirmation (2026-08-20)

All already fixed/clean from prior passes, reconfirmed this round without needing new changes:

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