Magical Century (mohuanshiji)

✅ 可玩

魔幻世纪

mohuanshiji

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

▶ 开始游玩 · Play Now

青岛站,作者"小宝(suannai)/西瓜太郎(gslxz)/桃心太郎",由"星月传奇"发布,属于"神话西游"(SJXY) 一脉,是 ES II 引擎下西游记(Journey to the West)神话世界观的一个独立改版分支——与本项目收录的 mhxy、rzrmud 同为这一神话西游世界观,但代码血统各自独立,并非彼此的换皮版本。新角色从"南城客栈"启程,唐僧、店小二、千里眼等经典角色以 NPC 身份驻场迎接新人,并在原有取经故事框架上加入了武当、移花宫等原创门派内容,是同类西游题材泥巴中内容比较丰富、系统比较完整的一支;注册流程里还有一步"您是否是中小学学生或年龄更小?"的自述式年龄提示,答 yes(或不理会)会被直接劝退,是这批档案里少见的、游戏本身自带的年龄门槛;死亡则有完整的阴曹地府体验——由"朱笔判官 崔珏"接引到"阴阳界",静候片刻后自动复活还阳,和同一 ES2 大家族的 `yszz`/`bmxkx2001` 是同一套设计。

English

A Qingdao-hosted branch of the 'Mythic Journey West' (SJXY) family, built on the ES II engine as an independent take on the Journey to the West mythos — sharing the same setting as sibling games mhxy and rzrmud in this collection but with its own separate codebase. New characters set out from the South City Inn, greeted by classic figures Tang Sanzang, the inn's waiter, and the far-seeing Clairvoyant (千里眼) as NPCs; the game layers in original sects such as Wudang and Yihua Palace not found in the source novel, making it one of the more content-rich entries in this sub-genre. Registration includes an unusually self-aware age gate that turns away anyone who answers yes (or doesn't answer) to "are you a primary/secondary school student or younger?", and death sends players through a full underworld sequence — escorted by Judge Cui Jue (崔珏) to the Yin-Yang Boundary before automatic revival, the same design shared with this collection's yszz and bmxkx2001.

README

深度功能测试新发现的 bug(详见 NOTES.md)

adm/daemons/logind.lpc 里有一行调试用的 printf("%O\n", ob),紧 挨在设定中文名字之前,每一个新玩家注册时都会看到(AGENTS.md §7.34)。已删除。(另外核实了 doc/help/newbie 里"食物/饮水初 始为空"的说法和实际代码不符——logind.lpc 明确无条件把两者初始 化为满值,是文档陈旧而非程序 bug,未做代码改动。)

在线试玩

https://mudlibs.fluffos.info/mohuanshiji/

管理员账号 / Admin account

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

本地运行

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

游戏端口:40092

NOTES · 移植与修复记录

mohuanshiji — 魔幻世纪 (Magic/Fantasy Century)

Archive: archives/魔幻世纪.rar (archive #100, the last one in this batch). Extracted mudlib root: raw/mhsj/mud/ (a config.cfg sits at raw/mhsj/, one level above the mud/ dir it points into — mudlib directory in the raw config is a Windows path e:/mhsj/mud). Port: 40092. Status: done (boots clean, full registration flow verified twice with real Chinese names including both gender branches, post-login look/score verified working every time, zero runtime errors in debug.log across all sessions).

What this is / lineage

config.cfg's own name field (GBK-decoded, no stale leftover this time) is 魔幻世纪, matching the archive's own filename — no rebrand mismatch to untangle here. raw/mhsj/readme.txt (credited to 小宝(suannai) / 西瓜太郎(gslxz) / 桃心太郎(iiiii), hosted for download by "星月传奇") states this codebase itself started life as an xyj (西游记/"Journey to the West")-derived ruleset — "本套源代码,修改了不少xyj中的BUG" ("this source fixed many bugs in xyj") — with 武当 and 移花宫 sects added as this site's own original content. adm/obj/master.lpc's header comment reads "for ES II mudlib, original from Lil, rewritten by Annihilator (11/07/94)" — the same ES II lineage shared by the large majority of already-processed archives in this project (directory layout adm/, feature/, std/, obj/, cmds/, d/, u/; securityd.lpc's ACL shape; feature/dbase.lpc's real non-simul_efun set/query). Several in-mudlib strings (logind.lpc's own comment header "神话世界·西游记· 版本4.50", a MUDLIB=="SJXY" check in total_players(), u/GSLXZ / u/suannai wizard homes matching the readme's credited authors) confirm this is a "神话西游"(SJXY)-descended xyj build, not a from-scratch design — consistent with, but not byte-identical to, any already-shipped sibling spot-checked (master.lpc, chinese.lpc, securityd.lpc all differ from the nitan/XO/ES2-Neolith/"hell" families' equivalents). Genuinely its own build, not a rebrand of one already in the project.

Notably not present in this lib despite the ES II ancestry: the nitan-family dbase-via-simul_efun architecture bug (§15) — this lib's feature/dbase.lpc-equivalent already implements set/query/delete directly, same as tiexuejianghu/syxjl/etc.

Fixes applied (with why)

§8h — convertd.lpc stray-backslash Greek/charset-table typo (43 instances, CRITICAL — broke the whole simul_efun boot)

adm/daemons/convertd.lpc builds several large lookup tables (Latin/Greek/ pinyin-tone character variants) as one-entry-per-line string literal arrays. 43 of these lines had a stray trailing \ immediately before the closing quote ("α\", instead of "α",), escaping the closing quote and swallowing every subsequent line into one giant malformed string literal until the parser hit the *next* real quote — this crashed the compile of convertd.lpc, which is #included/composed such that it broke simul_efun object loading generally, cascading into "couldn't find object '/adm/daemons/channeld'" runtime errors for every daemon whose create() fires after convertd in preload order (see the CHANNELD fix below — same underlying trigger). One entry ("α\",) also contained a legitimate hidden PUA codepoint (U+E5A2, an alternate glyph representation intentionally stored alongside each table entry, e.g. "Ω",/"β", — confirmed present and syntactically fine on *every other* table row, not corruption) — fixed with a Python line-indexed read/write that removed only the stray backslash and left the PUA character untouched, per the encoding-section's PUA caveat (don't trust a text editor / blind sed here). Found and fixed one instance first via lpcc on /adm/obj/simul_efun, then proactively scanned the whole file for the same shape (grep-style scan for lines ending \",) and fixed all 43 in one pass rather than one-at-a-time.

Case-sensitivity: adm/daemons/CHANNELD.lpc vs. CHANNEL_D macro (CRITICAL — broke ftpd and any other post-channeld preload daemon)

include/globals.h defines CHANNEL_D as "/adm/daemons/channeld" (lowercase) — but the file on disk, after the standard .C.lpc uppercase-extension rename (§ "Watch for uppercase .C"), was CHANNELD.lpc (all-caps basename, not just extension — a genuine Windows-case-insensitive-authoring artifact, same root class as §15g/§15k but for a #defined daemon path rather than an #include or literal data file). Even though channeld itself loaded fine in preload order (its own create() has no immediate dependency on the macro resolving), any LATER preloaded daemon that calls CHANNEL_D->do_channel(...) in its own create() (found via ftpd.lpc's create()) got *call_other() couldn't find object '/adm/daemons/channeld' — non-fatal individually (wrapped in catch() by master.lpc's preload()), but it flooded the boot log with repeated error-handler recursion output and meant the channel daemon was silently unusable from any of these call sites. Fixed by renaming the file to lowercase adm/daemons/channeld.lpc to match the macro (much lower blast radius than changing every CHANNEL_D/do_channel call site). Lesson for the catalog: the uppercase-.C-rename check (found on shenmo #73) needs to also cross-check renamed files against any #defined macro path pointing at them by a DIFFERENT case, not just check that the rename itself happened — 310 total uppercase-origin files existed in this lib; only this one (a core preloaded daemon) actually mattered.

Uppercase-.C rename encoding gap (15 files never GBK→UTF-8 converted — NEW finding)

convert_lib.sh's forced-text-extension allowlist (*.c|*.lpc|*.h|...) is a case-sensitive bash case match — an originally-uppercase .C file never matches it and falls back to file(1)'s guess, which misclassified 15 of these (raw GBK, CRLF line endings) as data (binary) and skipped them entirely — the same failure mode as the lib-#4 "CRCRLF confuses file" bug, just gated by extension case instead of line-ending shape this time. These 15 files were then manually .C.lpc renamed (matching every *other* uppercase file, most of which file DID correctly detect as text) but never had their actual byte content converted, surfacing later as Invalid UTF8 codepoint in string literal / raw 0xb9 0xa6...-style mojibake in the lpcc sweep, not at boot time (none of the 15 are preloaded/reached by the registration path). Found via a small Python script attempting bytes.decode('utf-8') on every .lpc/.h file in work/ and listing the ones that fail — cheap, worth running proactively on any lib with uppercase-.C files going forward, not just this one (files: clone/{SHUSHAN,HELL,MOON,LONGGONG,JJF}.lpc, adm/CL/{SHUSHAN,HELL,MOON,LONGGONG,JJF,QIANG}.lpc, d/city/npc/XIA.lpc, d/city/obj/npc/greeting.h, d/sea/npc/beast1.lpc, d/obj/quest/shuijingqiu.lpc — converted with iconv -f GB18030 -t UTF-8, falling back to -c for two files with a truncated trailing multi-byte sequence at EOF).

DOS-era Ctrl-Z (0x1a) EOF markers (11 files)

The same 11 adm/CL/*/clone/* files above (once GBK-decoded) also each had a trailing 0x1a (SUB/Ctrl-Z) byte — an old MS-DOS text-editor end-of-file marker, harmless in GBK/UTF-8 either way but an "Illegal character" hard compile error to this driver's lexer. Stripped with a one-line Python byte-replace once identified via the same lpcc sweep.

§14 — valid_override() upgraded to the 3-arg form (adm/obj/master.lpc)

Was the old 2-arg valid_override(file, name). Added the 3rd main_file parameter and an early-allow when main_file == SIMUL_EFUN_OB || main_file == MASTER_OB, matching the documented fix — cheap, correct, applied proactively (never actually triggered in a real boot here either, consistent with the catalog's note that this mostly only bites a bare lpcc compile of an #included simul_efun fragment).

§15h — is_chinese() GBK byte-range check (adm/simul_efun/chinese.lpc)

Was strlen(str)>=2 && str[0] > 160 && str[0] < 255. Fixed to strlen(str)>=1 && str[0] >= 0x4e00 && str[0] <= 0x9fff (CJK Unified Ideographs range). Without this every real Chinese name is silently rejected at registration — confirmed via the interactive test below that 秦风/秦风四/秦风五 all now register correctly.

§15h — check_legal_name() bound + sliding-window (adm/daemons/logind.lpc)

Byte-length bound strlen(name) < 2 || > 12 (message already said "一到 六个中文字", i.e. 1–6 Chinese characters, calibrated for 2 bytes/char) halved to < 1 || > 6. The sliding-window guard was i%2==0 && !is_chinese(name[i..<0]) (checking only even *byte* offsets, using a "rest of string" substring so is_chinese's own strlen>=2 requirement had something to bite on) — replaced with a plain !is_chinese(name[i..i]) checked at every index (every position is one full character now, no stepping needed, and a true single-character slice makes is_chinese's own length check trivially satisfied for every real position including the last character, which the old shape always failed).

§15p — excluded network/dns_master from adm/etc/preload

Standing policy; this daemon bootstraps a UDP intermud database against a hardcoded remote "boot server" unreachable from this sandbox. Removed the /adm/daemons/network/dns_master line from adm/etc/preload.

§15ab-style — guarded Mud_name() macro against dns_master never loading (include/net/macros.h)

Per §15p's own caveat ("necessary but not sufficient"), grepped for direct callers of the DNS/intermud subsystem outside preload/adm/daemons/network/ itself. adm/daemons/CHANNELD.lpc's intermud-channel relay branch (fires only when a player speaks on an "intermud":-flagged channel — xyj, es, jh, gwiz; not the sys/chat channels used during ordinary login, so this never actually fired during the interactive test below, but is a real latent crash for anyone speaking on those channels) and cmds/usr/mudlist.lpc/cmds/wiz/tmp.lpc all call the bare Mud_name() macro, which unconditionally did (string)DNS_MASTER->query_mud_name(). Changed the macro itself to (string)(find_object(DNS_MASTER) ? DNS_MASTER->query_mud_name() : CHINESE_MUD_NAME) — one fix covers every call site, degrading to the mud's own name constant instead of a 0->query_mud_name() runtime error when the daemon is (deliberately) never loaded. Confirmed no other network-subsystem macro (index_add/index_call/dns_log/dns_warning/ udp_port/mud_nname) is called from outside adm/daemons/network/ itself, so nothing else needed the same treatment.

§15w — log_error() no longer echoes compile *warnings* to a connected non-wizard (adm/obj/master.lpc)

This driver's compile-diagnostic text is lowercase "warning: " (verified by reading ~/src/fluffos/src/compiler/internal/lexer_utils.cc's prepare_logs()), and log_error() is invoked for every compile diagnostic, not just fatal errors — the original code showed the scary default error message to any connected non-wizard on ANY diagnostic, including harmless ones like our own §3 nosave-on-function warning that this driver itself only warns about. Gated the player-facing branch on strsrch(message, "warning:") == -1 (i.e. only show it when the message is NOT a warning) — still logged to the log file either way, just not echoed to the connecting/playing user as if it were a crash.

§15ab item 1 — missing !stringp(str) guard in the pre-login connection object's own receive_message() (obj/login.lpc)

feature/message.lpc (the real player body's copy) already guards with if(!msg || (len=strlen(msg))<1) return;, but obj/login.lpc's own copy (used for the NOT-yet-authenticated connection object, reached by every single fresh connect before make_body()) had no such guard at all — any write(0) reaching this object during the pre-login sequence (this driver's write() simul_efun doesn't static-type-check a mixed-typed 0 at the call site) would have crashed receive() on literally every connection, before any prompt appears. Added the same !stringp(str) guard as a defensive fix (no live crash of this specific shape was actually observed in testing here, but the asymmetry with the working copy was the exact shape of a previously-documented bug from haiyang2).

§15b — db_affected(int) stub (adm/simul_efun/file.lpc)

adm/daemons/databased.lpc (not preloaded, not reached by the registration/gameplay path tested) calls a never-defined db_affected(db) after every db_exec(). Restored as a simul_efun stub returning 1 (assume ≥1 row affected) per the documented precedent — this driver's DB package has no such efun and db_exec() itself returns 0 for INSERT/UPDATE/DELETE regardless, so every real call site's own db_exec()-return check is the actual signal; this only silences a compile-time "Undefined function" for a file that was never going to be exercised by this pass's testing anyway.

§8e — tail() efun call in an unused admin command (cmds/wiz/tail.lpc)

Reimplemented in plain LPC (read_file() + explode() + slice + write), matching the documented pattern exactly — non-fatal either way (dead wiz tooling), fixed since it was cheap.

§15ac — bare SAVE_EXTENSION (6 refs, adm/daemons/network/netmail.lpc)

Replaced with this driver's real autogenerated __SAVE_EXTENSION__ constant. Only reachable from the (deliberately unloaded) network/ subtree, so not exercised by testing, but cheap and correct to fix.

§15t variant 1 — absolute path in angle-bracket #include (1 file)

d/obj/books-nonskill/book-qujing.lpc's #include </d/qujing/obstacle.h> converted to the quoted form #include "/d/qujing/obstacle.h".

#include "../parent.h" (disallowed .., 1 file)

d/ourhome/honglou/npc/niu.lpc's #include "../honglou.h" rewritten to the real absolute quoted path #include "/d/ourhome/honglou/honglou.h".

Pre-existing typo: missing closing quote (2 files, §10-shape)

Confirmed NOT needed (checked explicitly)

Known content gaps (not fixed — genuine archive gaps, §13-style)

Registration-flow AND post-login-command test (verified twice, both genders)

Booted ~/src/fluffos/build-debug/src/driver config.fluffos from libs/mohuanshiji/ (port 40092), then in ONE continuous scripts/mudclient.py connection each time:

Run 1 (male): ""gbnoqinfengdy (confirm new char) → 秦风四 (Chinese name) → test12345 (password) → test12345 (confirm) → [email protected] (email) → m (gender) → 9 (accept default gift allocation) → y (confirm) → lookscorequit.

Run 2 (female): same shape with id qinfenge, name 秦风五, gender f.

Both runs: encoding/age-gate prompts answered, English id accepted, Chinese name accepted on the first try (validating the §15h fix), password set/confirmed, email accepted, gender accepted with the correctly gendered join broadcast (male: "…的小兄弟来到《魔幻世纪》求取真经" / female: "…的小姑娘来到《魔幻世纪》求取真经"), landed in /d/wiz/init's gift-allocation dialog, accepted the default allocation, and the game printed "好!祝你西天取经早成正果!" and moved the new character to the real start room 南城客栈 (/d/city/kezhan) with a full room description, exits, board, and NPCs (唐僧/店小二/千里眼) — 店小二's ambient greeting even correctly used the gendered term ("这位小姑娘" for the female run). score printed a complete, correctly-gendered character sheet (仙衔/职称/attributes/etc). quit produced a clean "欢迎下次再来!" goodbye with no hang. log/debug.log was completely clean (zero error/ undefined/recursion lines) across both full sessions, boot-to-quit.

lpcc sweep results

scripts/lpcc_check.sh (fresh run after all fixes above): 8316/8535 pass (97.4%). Remaining 219 failures triaged by category:

Re-verification pass: driver rebuild + formatter + WASM (2026-07-23)

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

Gates patched (loopback = 127.0.0.1, any 127.*, or an empty/malformed non-dotted-quad string, which is what current WASM builds return):

Admin account: fluffos / Mud@2026, Chinese name 浮浮, granted (admin) via /adm/etc/wizlist (file normalized CRLF→LF). Verified: registration via the real flow (login banner even shows 目前权限:(admin)), re-login, update /d/city/kezhan.lpc succeeds. Saves at data/login/f/fluffos.o + data/user/f/fluffos.o (not gitignored). Fresh normal registration (秦风/testqa, female branch) re-verified end-to-end and the test char removed; debug.log clean.

Fail-closed retrofit (2026-07-24)

The loopback carve-out above originally ALSO treated any empty/ non-string/unparseable IP as trusted-local (fail-open, defensive against an older WASM query_ip_number() bug now fixed upstream). Tightened to strict loopback only ("127.0.0.1", "::1", "127." prefix) across band.lpc (×4), securityd.lpc's match_wiz_site, ipd.lpc (falls back to "未知地区" for unparseable input instead of "本地连接"), and logind.lpc's local_conn flag. Re-verified loopback login/look/update/ quit all still work after tightening.

深度功能测试(第二轮,2026-08-03)

之前的会话只测过注册流程,本轮做了完整的 §10.7 深度功能测试。 proactive 检查 AGENTS.md 已归档的四类常见坏味道:feature/ command.lpc 里的"private nomask command_hook"只是一行历史注释 (// private nomask int command_hook(...)),真正生效的声明早已 是干净的 nomask int command_hook(...);命中了一处 adm/daemons/ logind.lpc 里的 printf("%O") 调试残留(新增 mohuanshiji 到 AGENTS.md §7.34 确认实例列表);未命中 unguarded MESSAGE_D->、 stat/water 键名、§8.9 坏 age 判断。

完整流程验证:注册全新角色(沐魔幻/id mhsjqin,途中还发现 了一步之前没记录过的"您是否是中小学学生或年龄更小?(yes/no)"年 龄自述关卡——答 yes 或不理会直接被系统劝退,答 no 才能继续注册, 纯属游戏自带的内容提示,不是 bug),从"南城客栈"(同一个西游记 ES2 大家族的经典布景,唐僧、店小二、千里眼 NPC 驻场)出发,向 "朱雀大街"的"疥顶小僧"(这批西游记题材姊妹档案里反复出现的同一个 高战力 NPC,combat_exp 50000)发起战斗——完整交手数回合后被击昏 迷致死,送往"阴阳界"由"朱笔判官 崔珏"接引(和 yszz/bmxkx2001 是同一套死亡/复活设计),静候片刻后自动复活还阳,score 数据完 整(气血显示"重伤"正在恢复中,其余状态正常);quit 干净退出。 全程 debug.log 零报错。

顺带核实一处文档与实际行为不一致,判断为陈旧帮助文档而非 bug, 未做代码改动doc/help/newbie 第二条明确写着"刚进入时,你的 食物和饮水都是空的",但实测新角色食物/饮水槽创建时就是满的 ("正常"状态)。查了 logind.lpc 里的赋值语句—— user->set("food", user->max_food_capacity()) /user->set("water", user->max_water_capacity())——这是一行无条 件执行、没有任何坏掉的前置判断的正常代码,明确就是设计成"创建时 直接给满",不是 §8.9 那种"卡在错误物件的判断条件"式 bug。判断为 帮助文档没有跟上后来的某次平衡性调整,按项目惯例不去改动这行明 确、正常工作的代码去迁就一份过时文档。

未覆盖范围:拜师、留言板、当铺/兵器铺购物因时间原因未实测。

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

§7.100 sweep (2026-08-19)

Fixed the corpus-wide inherit ROOM; ... replace_program(ROOM); redundant-replace bug (AGENTS.md §7.100). 274 live occurrences deleted: 269 via scripted sweep (fix_710_room.py), plus 5 hand-fixed roommaker-tool occurrences across 3 tool copies (clone/misc/roommaker.lpc, obj/roommaker.lpc — simple variant; cmds/wiz/roommaker.lpc — "room_code"/str 3-occurrence variant). 7 already-commented-out instances left untouched. One occurrence NOT fixed: d/city/center2.lpc is already syntactically broken independent of this bug (unquoted strings like set(short, 十字街头), stray ?? token, no semicolons at all in create()) — never compiles regardless, out of scope. No real .lpc source found under work/data/. Verified via build-debug driver boot: clean compile, port 40092 listening, zero new "cannot replace"/"cannot bind" debug.log lines. Pre-existing untracked test-account debris (data/{login,user}/m/mhsj{dive,qin}.o) confirmed left untouched.

Round-four re-test (2026-08-20): 拜师/留言板/当铺·兵器铺 + standing-checklist sanity pass

Covered exactly the three areas round-two/round-three had explicitly left untested for time, plus a sanity re-check of the standing bug catalog items called out for this pass.

Checklist sanity pass (all confirmed already correct/intact, no regressions, nothing new needed):

1. 拜师 (sect apprenticeship) — mechanism confirmed working (clean rejection). Located the command (cmds/std/apprentice.lpc) and a reachable, no-existing-family-required sect master, 秦琼 (d/jjf/npc/ qinqiong.lpc, in d/jjf/keting正厅). apprentice qin (id qin, not qinqiong — the alias list is qin qiong/qin/shubao/etc., space- separated multi-word ids only) correctly resolved the target and triggered attempt_apprentice(), which rejected on the admin test character's insufficient combat_exp (< 100000) with an in-character reason ("这位小兄弟还是先去跟本府家将打打基础吧!") — exactly the "reject with a sensible reason" clean-pass shape. Re-verified identical on a completely fresh driver boot. No crash, no debug.log/boot-log error either time.

2. 留言板 (board posting) — confirmed fully working. post board at 南城客栈留言板 (/obj/board/nancheng_b.lpc, inherit BULLETIN_BOARD with no stray replace_program() — matches the already-applied §7.86/ §7.100 fixes) entered the line-editor cleanly; two content lines plus a title line were all accepted (no "什么?" misroute on any line, ruling out the §7.114 shape), . ended the post with "留言完毕。", and read 1 board immediately after showed the full multi-line content correctly saved with the right author/timestamp. Persisted correctly across a full driver restart. Test post removed afterward via the in-game discard 1 board command (admin-authored, cleanly deletable) to leave the board back in its pre-test empty state — data/board/ directory removed after, no residue.

3. 当铺 (pawnshop) / 兵器铺 (weapon shop) — REAL BUG FOUND AND FIXED.

当铺 (/d/city/dangpu.lpc, inherit HOCKSHOP) worked cleanly: value/pawn/sell/retrieve <stamp> all round-tripped correctly on a cloned test weapon (典当 480 银, 卖断 640 银, 赎回 560 银 — sane, consistent conversion math each time). list correctly reported "当铺 目前没有任何货物可卖" since this pawnshop has no vendor_goods of its own (by design — it only deals in player-pawned items, not a curated sale list).

兵器铺 (/d/city/bingqipu.lpc) crashed hard: list (i.e. simply browsing the shop's stock, the very first thing any player does before buying) threw an uncaught driver-level error on every single call:

执行时段错误:*call_other() couldn't find object '/clone/armor/whip'.
程序:/feature/vendor.lpc 第 48 行

Root cause: feature/vendor.lpc's do_vendor_list() does goods[name[i]]->query("name") on each vendor_goods string value directly (an implicit lazy-compile call_other), with no existence guard — and every one of the vendor 萧萧's (d/city/npc/xiaoxiao.lpc) 10 vendor_goods entries pointed at /clone/armor/<item>, none of which resolved, because the whole directory on disk was clone/ARMOR/ (uppercase) with 14 of its 21 item files ALSO carrying an uppercase basename (WHIP.lpc, SWORD.lpc, MACE.lpc, etc.) — the exact same "Windows-authored uppercase directory/filename vs. a lowercase path baked into the referencing code" shape this lib's own adm/daemons/CHANNELD.lpc-vs-CHANNEL_D bug already documented above, just one level up (a whole directory, not one file) and hitting a much more central, always-reachable code path (list in ANY shop using this NPC's item catalog) instead of a rarely-exercised daemon call site. This made the entire 兵器铺 permanently unusable for every player — list never worked, and buy/value on any of its 10 items would have hit the identical unguarded -> call in buy_object()/complete_trade() the moment anyone tried.

Fix: renamed clone/ARMOR/clone/armor/ and the 14 uppercase-basename files inside it to lowercase (bang.lpc, blade.lpc, dun.lpc, fork.lpc, jinjia.lpc, kui.lpc, mace.lpc, pao.lpc, pifeng.lpc, shoes.lpc, spear.lpc, staff.lpc, sword.lpc, whip.lpc, zhi.lpc) — all via git mv, content untouched, matching every /clone/armor/<lowercase-id> reference already in the code (corpus-wide grep confirmed exact 1:1 coverage, no orphaned or newly-broken references either direction).

Extended sweep of the same bug class, whole clone/ tree. Given this was a real, live-reachable crash and the fix pattern is mechanical and low-risk, ran the same "uppercase-basename file/dir vs. a lowercase reference elsewhere in the corpus" check across all of clone/ (not just armor/), cross-referencing every /clone/... string literal in every .lpc/.h file corpus-wide against the actual on-disk filenames. Found 31 more genuinely-broken (referenced-but-case-mismatched) files, all suffering the identical fate — silent, unconditional ->query()/new() crashes the moment anything actually touched them (NPC weapon-equip calls, gift-item grants, event/魔幻-item spawns): clone/{dan,qinghong,jiasha,tiekui,yinjia,bi,dun}.lpc (top-level), clone/gift/{lingzhi,bingtang,xisuidan,xiandan}.lpc, clone/bq/bang.lpc, and 18 files under clone/mohuan/ ({spear,qin,fork,armor,jindao,pifeng,staff,whip,cloth,shoes,shendan, sq,qingting,dun,mobang,axe,kui,sengxie,xiandan}.lpc). All renamed the same way (git mv, content untouched). Re-ran the full corpus-wide scan afterward: zero remaining case-mismatched /clone/... references anywhere in the lib. (A separate, larger set of /clone/... references — d/hen/-adjacent weapon items, d/wuguan/'s CLOTH_OB/WEAPON_OB macro targets, a handful of /clone/gift/ and /clone/book/ paths — genuinely have no on-disk file under ANY case variant; left alone as pre-existing content gaps, not this bug.)

Verification: live-tested end-to-end on a freshly rebooted driver (new PID, full recompile) — 兵器铺's list now shows all 10 weapons with correct prices, buy mace from xiao completed a real gold-for-item transaction (deducted the listed price, item appeared in inventory), and the full 当铺 value/pawn/sell/retrieve cycle was re-run end-to-end on the purchased item with correct currency math throughout. Zero errors in the boot log across both driver runs (pre-fix crash was player-visible in-session; post-fix, list/buy on this and every other renamed item path produced no error at all). Driver killed by exact PID (kill -TERM, not pkill) after testing. Admin account (fluffos) inventory/gold left in its post-test state (matches this lib's established convention of leaving the working admin seed account as a normal, actively-used account); the synthetic board test post and its resulting data/board/ save file were both removed, and the only remaining untracked debris is the pre-existing mhsj{dive,qin} test account pair already documented above (left untouched).

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

Round-four re-verify (2026-08-24): re-ran 拜师/留言板/兵器铺, found and fixed a real §7.117 instance

The task brief for this pass named the same three areas as the 2026-08-20 entry above (拜师/留言板/当铺·兵器铺) — that entry already covers them and fixes the 兵器铺 case-mismatch crash. Re-verified all three live on a fresh driver boot before doing any new work, since the brief also asked to specifically check for AGENTS.md §7.117 (the apprentice/bai/recruit first-time-applicant guard gap, documented after the 08-20 pass) — which the 08-20 entry did not check for.

Re-confirmed clean, no regressions: NPC-rejection path (apprentice qin at low combat_exp, exact same 秦琼/d/jjf/keting setup as before) still gives the same clean in-character rejection; board post/read/discard round-trip at 南城客栈留言板 still works end-to-end; 兵器铺's list/buy still work (clone/armor/ stays lowercase, no case-mismatch regressions found anywhere under clone/).

Found and fixed a real §7.117 instance. cmds/std/apprentice.lpc's "the target already offered to recruit me, I'm confirming" branch (the ob->query_temp("pending/recruit") == me branch, entered when a player runs apprentice <master> a second time after the master's attempt_apprentice()/recruit accepted them) compared me->query("family/family_name") != ob->query("family/family_name") with no guard for me having no family yet — exactly the pattern in AGENTS.md §7.117. For any legitimate first-time apprentice (the overwhelmingly common case — everyone's first sect join goes through this exact code path), me->query("family/family_name") returns 0 (no family set), (string)0 casts to "0", which is never equal to the master's real family name, so the check always misfires into the "betray your old sect" branch: wrong flavor text ($N决定投入$n门下 instead of the correct $N决定拜$n为师), plus me->set("score", 0) and me->add("betrayer", 1) incorrectly applied to a brand-new apprentice who never had a prior sect to betray.

The sibling file cmds/std/recruit.lpc (the master's side of the same recruit/confirm transaction) already has the correct guard at its analogous check (line 63: if ((ob->query("family")) && (...))), with an explicit comment // follow modified by elon 09-10-95 to fix a bug in 1st time recruit acknowledging this exact bug class — the fix was applied once to recruit.lpc decades ago and never ported to its twin, apprentice.lpc. Matches the general §7.117 write-up's own observation about this exact half-fixed-pair shape.

Fix (cmds/std/apprentice.lpc, in the recruit-confirm branch):

// before:
if ((string)me->query("family/family_name") != (string)ob->query("family/family_name")) {
// after:
if ((me->query("family")) && ((string)me->query("family/family_name") != (string)ob->query("family/family_name"))) {

Live-verified end-to-end on a fresh driver boot: bumped a family-less admin test character's combat_exp to 200000 (temporarily, via a throwaway wizard command created and removed within this session — not left behind), went to 秦琼 at d/jjf/keting, ran apprentice qin twice (first call triggers attempt_apprentice() -> NPC recruits the player; second call is the player's confirm). Before the fix this would have shown the wrong "決定投入...門下" betrayal message and zeroed score; after the fix it correctly shows "秦琼决定 收你为弟子。...恭喜您成为将军府的第三代弟子。", and score correctly shows 师承:将军府秦琼 / 将军府第三代弟子. Reverted the test character's family/title/betrayer/combat_exp/class fields back to pristine afterward (also bought+destroyed one 兵器铺 item along the way while re-verifying the shop — correct gold deduction, 19→11 两黄金 for an 8-两黄金 item — left the resulting gold balance as the account's normal post-test state, matching this lib's existing admin-account convention). lpcc --batch and a full driver reboot both clean after the fix, zero new debug.log errors. No corpus-wide sweep of this exact shape was attempted here (each instance needs hand verification per the general §7.117 write-up); left for a future dedicated sweep as already flagged there.

AGENTS.md §7.19: enable_player() reentrancy guard (2026-09-01)

Same corpus-wide bug class as mhxy/wuhanzhan: feature/command.lpc's enable_player() wraps enable_commands() and is unconditionally reachable from an NPC's init() via setup()/reset_me() (confirmed on this lib's own d/*/npc/zhangmen*.lpc-family NPCs, matching mhxy's originally-documented d/xueshan/npc/zhangmen.lpc pattern). Calling enable_commands() on an object that's already living() makes the driver re-invoke that object's init() as a side effect; since init() calls back into enable_player(), that is genuine same-call-stack reentrancy that repeats until "Too deep recursion" aborts a room's first-ever visit.

Fixed with a true reentrancy flag (nosave private int in_enable_player_now;), NOT a bare if (living(this_object())) return; guard — this lib's feature/damage.lpc revive() and cmds/std/sleep.lpc wakeup()/wakeup2() all legitimately re-invoke enable_player() while the object is still living() (that's how a fainted/asleep character gets commands back), so a living()-gated guard would silently break every one of those real re-enables. enable_player()'s single body has no early return statements, so the flag is set at entry and cleared once, before the function's fall-through end. Verified with a single-file lpcc compile check (exit 0, no errors) against feature/command.lpc.