Gods and Demons: Legend of the Journey West (shenmo)

✅ 可玩

神魔(西游记之神魔传说)

shenmo

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

▶ 开始游玩 · Play Now

属于"ES II / Neolith"引擎家族(与本项目中的神州、随缘洗剑录等同宗),是一个从 1994 年一路开发维护到 2013 年的长寿老牌 MUD,多位巫师先后参与过其代码,是本项目转换过的体量最大的资料包之一(三万余个源码文件)。创角时可以选择人类、仙族或妖魔三大种族,之后从长安城"南城客栈"开始自己的西游之旅——地图和剧情都换成了西游取经沿途的妖魔鬼怪与神仙世界,整体延续属性天赋、师承门派、武学修炼的经典武侠/仙侠养成框架;南海普陀山门下更设有"掌门大弟子"称号体系,随着实际游玩在不同角色间自然更替传承。

English

A long-lived ES II/Neolith-engine game — sharing its engine lineage with sibling games shenzhou and Suiyuan Sword Legend in this collection — developed and maintained continuously from 1994 to 2013 by a long succession of wizards, and one of the largest archives in this collection at over 30,000 source files. Characters choose to be human, immortal, or demon before setting out from the South City Inn in Chang'an on a Journey-to-the-West-themed adventure through the demons and deities encountered along Tang Sanzang's pilgrimage route, built on the classic wuxia/xianxia progression of stats, sects, and martial cultivation. The South Sea Guanyin's Mount Putuo order even carries a persistent 'head disciple' title that naturally passes between characters as the game is played over time.

README

内容亮点

在线试玩

https://mudlibs.fluffos.info/shenmo/

管理员账号 / Admin account

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

本地运行

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

游戏端口:40067。由于资料包体量巨大,编译预加载会比其他游戏稍慢 一些,属正常现象。

NOTES · 移植与修复记录

shenmo — notes

Identity

Scale (why several steps below deviate from the small-lib default)

Encoding

Fixes applied (with why)

1. §15h, is_chinese() GBK byte-range bugadm/simul_efun/chinese.lpc: if( strlen(str)>=2 && str[0] > 160 && str[0] < 255 ) return 1; (a GBK lead-byte range check, comparing a full UTF-8 codepoint against 160-255 — always false for real Chinese text under this driver) → fixed to if( strlen(str)>=1 && str[0] >= 0x4e00 && str[0] <= 0x9fff ) return 1; (CJK Unified Ideographs codepoint range, strlen>=1 since one UTF-8 index is already one full character). Confirmed this is the function actually used by registration (traced check_legal_nameis_chinese, both in the live logind.lpc/adm/simul_efun/chinese.lpc chain). 2. §15h, named.lpc's PATH() sharding macroadm/daemons/named.lpc: #define PATH(name) (name[0..1] + "/" + name) ("first GBK character" under the old byte-indexed convention, now grabbing the first TWO UTF-8 characters) → fixed to name[0..0] (first character only). 3. §15h, check_legal_name() in adm/daemons/logind.lpc — two sub-fixes in the same function: - strlen(name) < 2 || strlen(name) > 12 (message text explicitly says "一到六个中文字" — 1 to 6 Chinese characters — the byte-doubled bound of 2-12 was calibrated for the old 2-bytes-per-character GBK convention) → halved to strlen(name) < 1 || strlen(name) > 6. - if( i%2==0 && !is_chinese(name[i..<0]) ) (the i%2==0 gate existed to land on every OTHER byte position — the lead byte of each 2-byte GBK character; under UTF-8 every index is already one full character) → dropped the i%2==0 && entirely, so every character position is checked, matching §15h's item 3 pattern exactly. 4. §8d/§15o, missing master::get_include_path() — found 1706 files throughout the content tree (d/, obj/, cmds/, kungfu/, daemon/, quest/, u/) using #include <flavor.h>-style angle brackets for a same-directory "flavor" header (e.g. d/kaifeng/ground.c #include <ground.h>, d/kaifeng/npc/bing.c #include <greeting.h>) — identical shape to the es1_win/esI lineage's documented §8d bug. Crucially confirmed zero of these 1706 hits are inside adm/daemons/, adm/obj/, or adm/simul_efun/ (grep -c '^\./adm/daemons\|^\./adm/obj\|^\./adm/simul_efun' on the hit list returned 0) — meaning none of the preload-critical bootstrap chain is affected by the §15o "no VM context at preload time" timing gotcha; the get_include_path() master apply alone is sufficient for every one of these 1706 files since they're all reached lazily, mid-connection, well after master/simul_efun/logind are loaded. Added the standard: ``lpc string *get_include_path(string file) { string *parts = explode(file, "/"); if (sizeof(parts) <= 1) return ({ "/", ":DEFAULT:" }); return ({ "/" + implode(parts[0..<2], "/"), ":DEFAULT:" }); } ` to adm/obj/master.lpc. This is the reason the physical "convert local <x.h> includes to quoted form" pass inside convert_lib.sh was deliberately killed partway through (see "Deviations from the standard pipeline" below) — the master-apply fix is a complete, driver-level substitute for the file-by-file quote conversion for every one of these 1706 cases, confirmed by the interactive test successfully reaching and lazily-compiling several of the exact affected files (d/city/kezhan.lpc, d/city/npc/xiaoer.lpc, d/city/npc/liwu7.lpc, all originally using <ansi.h>/local headers) with no Cannot #include errors anywhere in debug.log. 5. §3, staticnosave — 299 files touched by the blanket word-boundary sed (function declarations only; no bare array/other §15f-family issues found in this lib). §3 counterexample confirmed and reverted: this lib uses "static/CRASHES", "static/security", "static/PURGE", etc. as a log_file() subdirectory-naming convention (same idiom as moniHuafu/yanhuangwuhun) — real, pre-existing seed data exists on disk at log/static/{CRASHES,security,PURGE,gift,promote,...}. The blanket sed corrupted 82 files' string literals to "nosave/..."; reverted all 82 with a targeted sed -i -E 's/"nosave\//"static\//g' scoped to only the files flagged by grep -l '"nosave/'. Verified zero "nosave/ string-literal hits remain and all 82 "static/ literals are restored. Also checked, not present: no #define nosave static/#define protected static compatibility shim anywhere in include/*.h (the §15z collision case) — confirmed via direct grep, nothing to revert there. 6. New pattern, not previously seen in this project: uppercase .C extension rename363 files throughout the content tree (obj/npc/LUOHAN1.C, d/newjob/longzhu/obj/*.C, d/quest/newequip/**/*.C, daemon/class/**/*.C, etc.) use an uppercase .C extension instead of the usual lowercase .c — genuine LPC source (spot-checked several: real inherit ITEM;/set_name()/create() object bodies, not driver C source or data). convert_lib.sh's rename step only globs lowercase *.c (case-sensitive on this Linux host), so these 363 files were untouched by the normal rename pass and would have stayed permanently unloadable (any code path expecting to find foo.c/foo.lpc at that path would fail) — worked fine on the original author's Windows/case-insensitive dev filesystem, the same root cause class as §15g/§15k, just applied to whole compilable objects rather than #includes or plain data reads. **Fixed with an additional find . -name "*.C" -print0 | xargs -0 ... mv ... .lpc pass after the normal convert_lib.sh run, alongside the same literal-.c"-reference re-check (found and fixed 6 more stray refs this exposed). Worth adding to AGENTS.md's catalog** — grep any new lib for find . -name "*.C" early, since a case-sensitive-only rename glob is an easy thing to miss when every other lib in this project so far has used lowercase .c uniformly. 7. §15w, log_error() broadcasting ordinary compile warnings as scary player-facing messages — confirmed via a live before/after test (see "Registration + post-login test" below): adm/obj/master.lpc's log_error() did if(this_player(1)) efun::write("编译时段错误:" + message+"\n"); unconditionally, and this driver funnels every soft compile *warning* (Illegal to declare nosave function, Unknown #pragma, ignored — this lib's #pragma optimize/#pragma save_binary directives aren't recognized by this driver — Unused local variable, Number of arguments to 'X' disagrees with previous definition) through the identical apply as genuine fatal errors. Because a brand-new character's first look/score/etc. is very often the FIRST time in a driver session that a given room/NPC/command file gets lazily compiled, a fresh registrant saw a wall of 编译时段错误:...warning: ... lines that look exactly like real crashes, immediately after finishing registration and again on the very first look. Fix: gated the broadcast on the message NOT containing "warning:" (if(this_player(1) && strsrch(message, "warning:") == -1) efun::write(...)) — still writes every message (including warnings) to the log file via the unconditional efun::write_file(home + "log", message); line right below, just stops broadcasting harmless warnings to the connected player. Verified fixed: re-booted the driver after this edit and re-ran a full fresh registration (new id qfengliu/name 秦风六) — zero 编译时段错误 lines appeared anywhere in the transcript despite look/score/quit being the very first compile of cmds/std/look.lpc/cmds/std/score.lpc/ cmds/usr/quit.lpc in that driver session (confirmed via debug.log: 334 warning:` lines still logged to file, correctly suppressed from the player).

Confirmed NOT needed (and how confirmed — read the actual source, not guessed)

Known, not fixed (pre-existing content issue, documented per AGENTS.md's own precedent)

Deviations from the standard pipeline (and why, given the file count)

Boot

Registration + post-login test (the critical, most-important verification)

Ran the FULL flow in single continuous mudclient.py connections, reading the actual logind.lpc input_to callback chain first (encoding → if_young → get_id → get_new_id → confirm_id[auto] → get_name → new_ad_password → confirm_ad_password → new_password → confirm_password → get_email → get_webpage → get_icq → make_body[auto] → get_zhongzu → get_gender → confirm_gift[auto] → enter_world) rather than guessing the shape from prompt text — confirmed the encoding() prompt itself is actually SKIPPED (its whole function still exists, but logon()'s #ifdef GB_AND_BIG5 branch is false in this build since GB_AND_BIG5 is commented out in mudlib.h, so it calls encoding("gb",ob) directly instead of ever prompting the player) — the first REAL prompt a connecting player sees is the "进入(Enter)/离开(Exit)" youth-protection gate, not an encoding choice. Also discovered by running the test that the gift/talent-allocation menu (0-3 to re-roll a stat, 9 to accept) requires an explicit y confirmation after 9 ("你确定接受当前的天赋设置吗?[y/n]") — not documented anywhere in the prompt text itself, found only by watching an early attempt's look/quit sends get silently swallowed as (invalid) answers to this confirmation.

Final clean run (after the §15w fix, fresh id qfengliu / real Chinese name 秦风六, one continuous connection):

1                    -> youth-gate "进入" (enter)
new                  -> trigger new-player registration
qfengliu             -> English id (accepted, unique, 3-8 lowercase letters)
秦风六                -> Chinese name (ACCEPTED — confirms the is_chinese/check_legal_name fixes)
admin12345           -> admin password
admin12345           -> confirm admin password
player12345          -> regular password
player12345          -> confirm regular password
[email protected]        -> email
(blank)              -> webpage (optional)
(blank)               -> ICQ (optional)
1                    -> race: 人类 (human)
m                    -> gender: 男性 (male)
9                    -> accept current gift/talent allocation
y                    -> confirm gift acceptance
look                 -> REAL room description of 南城客栈 (South-city Inn, the actual
                         configured START_ROOM), full exits list, board, 3 NPCs listed
score                -> REAL character sheet, correctly showing "秦风六(Qfengliu)",
                         correct age/gender/stats/etc.
quit                 -> clean quit message, connection closed normally

Zero 编译时段错误/error/crash lines anywhere in this final transcript, and debug.log for this session shows only the one pre-known emoted.o restore warning plus 334 ordinary compile warning: lines (correctly suppressed from the player by the §15w fix, still logged to file). Re-ran this exact sequence twice (ids qfengs/秦风五 and qfengliu/秦风六) with identical success both times, confirming this is reproducible and not a one-off.

lpcc status

Full sweep skipped (mega-lib, see above). Individual lpcc runs against the bootstrap-critical chain (master, named, logind, securityd, chinese, and the actual start room d/city/kezhan) all passed clean (exit 0, no compile errors) before the first full driver boot.

Rebuilt-driver / formatter / WASM re-verification pass (2026-07-23)

1. LPC formatter applied across all 33,199 .lpc files in work/ (~703MB): {"total":33199,"written":32148,"wouldChange":0, "unchanged":625,"errors":426} — took a few minutes given the file count, run in the background per this lib's own established mega-lib-scale conventions. Checked for the ::fn()-after-( formatter bug found on tianxia/shujian2008/sjtx2/ syxjl this same pass (see tianxia/NOTES.md for the full writeup) — zero hits of the (: : corruption signature anywhere in this lib's 33k+ reformatted files. Spot-verified the three critical fixes survived reformatting intact: feature/command.lpc's command_hook still plain nomask (with the original commented-out private line preserved as a comment), adm/obj/master.lpc's §15w strsrch(message, "warning:") == -1 guard, and its §8d/§15o get_include_path() apply. 2. Native re-test against the rebuilt build-debug/src/driver: booted clean in a few seconds despite the 33k-file scale (zero fatal errors, only the one already-documented emoted.o restore warning plus ordinary compile warnings). Full registration verified end-to-end via mudclient.py, following the exact flow already documented above (youth-gate → new → id → Chinese name → admin password ×2 → regular password ×2 → email → optional webpage/ICQ → race → gender → gift accept/confirm → world): id smfmtb, real Chinese name 秦风壬, reached 南城客栈 (the documented start room), look showed the correct room, score showed a correctly-populated character sheet, quit produced the game's own farewell text. debug.log: zero error in error handler/denied/undefined function/bad argument lines beyond the one pre-known emoted.o restore message. No new fixes needed — confirms the reformatted 33k-file tree is still fully sound against the rebuilt driver. 3. WASM test: per this pass's own scope note for mega-libs, expected this might be slow/memory-heavy enough to just document a timeout/hang rather than force it through — it was not: the whole scripts/wasm_client.js run (copying work/'s ~700MB into MEMFS, compiling the preload chain, and driving a full registration) completed in under a minute, peak host memory usage stayed modest (~4.2GB used, ~18GB available throughout, no swap pressure beyond the pre-existing baseline from other concurrent agents' sessions on this host). Full registration completed successfully under wasm, same flow as the native test above (id smwasma, real Chinese name 秦风癸), reaching 南城客栈 with look producing the correct room and quit producing the correct farewell — a genuinely full, working wasm playthrough for a 33k-file mega-lib. Two caught, non-blocking runtime errors observed along the way, both logged but neither interrupting registration: - The already-documented emoted.o restore-format error (same as native, unrelated to wasm). - A new, minor query_ip_number()-adjacent finding: adm/daemons/ipd.lpc's seek_ip_address(ip) does user_ip = explode(ip, ".") then unconditionally indexes user_ip[1] for some branches — since query_ip_number() doesn't return a real dotted-quad under wasm, explode() yields a single-element array and indexing user_ip[1] throws Array index out of bounds. Called from logind.lpc's confirm_gift()enter_world() path (an ISP-routing/regional lookup, not an actual login gate), so — unlike shujian2008/tianxiawuxue's hard-rejecting is_banned()/is_valid() checks — this one is purely cosmetic/non-blocking here: the driver's own error handler catches it and registration proceeds straight through to the game world regardless. Not patched — same "known query_ip_number() wasm limitation, not a mudlib bug" reasoning, just a different, non-blocking manifestation of it than seen on other libs this pass. Assessment: shenmo is fully playable under wasm, the best possible outcome for a mega-lib, and the mega-lib-scale wasm risk flagged in this pass's own brief did not materialize.

WASM-enablement pass (2026-07-24)

Standard four-change pass (AGENTS.md §1.3b/§1.3e/§1.5), applied with targeted edits only (mega-lib — no sweeps):

1. Loopback-allow (empty/non-string/127.* IP treated as loopback): - adm/daemons/logind.lpc logon() (~line 179) — the is_strict_banned() gate, the !ip_name kick AND the per-character IP-format kick (kicked out, Non_number — this one destructed every WASM connection, since WASM's garbage IP contains non-digit chars) are all now skipped for loopback/malformed IPs. - adm/daemons/logind.lpc enter_world() (~line 1086) — the create_char_banned()/is_banned() move-to-guest-room gate skipped for loopback. - adm/daemons/band.lpcis_banned(), create_char_banned(), is_strict_banned() all short-circuit to 0 for loopback/localhost/malformed sites. - adm/daemons/ipd.lpc seek_ip_address() — returns "本地" for loopback/malformed IPs instead of crashing on user_ip[1] (previously documented-only WASM finding, now fixed). 2. Uptime gate: none (uptime() in logind is only bookkeeping for the newid-temp cleanup). Anti-flood: no per-IP throttle; the newid/<id> temp guard is per-ID double-registration protection (cleared every 300s / on restart), left alone as game logic. 3. Admin seeded: fluffos / Mud@2026 / 浮浮 → (admin) appended to /adm/etc/wizlist (3rd wizlist column = login-site restriction, left empty = unrestricted). Registration also required the lib's separate "管理密码" (recovery): Admin@2026. Save files: work/data/login/f/fluffos.o, work/data/user/f/fluffos.o (data/ not gitignored). Verified: update /cmds/usr/bjtime → 重新编译 成功. 4. Retest: fresh registration (smqfb/秦风, deleted after test) into 南城客栈 with look/score/quit OK. debug.log: only the pre-existing restore_object(): Illegal mapping format while restoring emote (known emoted.o data quirk, documented in earlier passes). Removed one runtime-churn file the boot created (u/snowtu/data/user/s/ sajia.dzxy). Registration flow gotcha (for future scripting): after email there are TWO skippable prompts (homepage AND ICQ/QQ) — a single empty send desyncs the race prompt.

Retrofit: fail-closed loopback check (2026-07-24)

The loopback-allow gates above were originally written per the (now superseded) defensive instruction to also treat an empty/non-string/ malformed query_ip_number() result as loopback, since older WASM driver builds returned garbage. That driver bug is now fixed upstream (query_ip_number()/resolve() return real values under WASM too), so the "malformed IP = trust it" fallback was a fail-open bypass with no remaining justification. Tightened every gate listed above to the strict pattern: loopback is ONLY ip == "127.0.0.1", ip == "::1", or a leading "127." prefix — a non-string/empty/malformed IP is now treated as untrusted/remote and subject to the gate normally, not silently allowed through. Retested: fluffos login (127.0.0.1, real value under the current driver) still passes every gate; debug.log stayed clean of denied/undefined function/error in error handler.

深度功能测试 / Deep functional test (2026-07-25, AGENTS.md §10.7)

One continuous native-driver session (plus deliberate reconnect/soak sessions afterward), following §10.7's checklist. Read doc/help/newbie first (「南城客栈」start room, food/water/气/内力/法力 basics, 拜师/学艺 flow, wimpy safety note, weak starter-NPC list, "if you can't win, quit" advice — note the help text's own body text repeatedly says "仙侣情缘" instead of "神魔传说", a leftover from whichever earlier game this help file was copied from; cosmetic/content, not touched).

Test characters

Safe-sparring mechanism (found, not live-reachable within budget)

d/city/obj/muren.lpc ("木人", a training dummy) has exactly the documented shape (accept_fight() copies the attacker's own skills/stats onto itself before the fight, so a spar against it can never be a mismatch) — but grepping the whole live tree found no room's "objects" mapping references this specific file; it is dead content (see also d/obj/misc/muren.lpc, also unreferenced). Sibling copies exist and ARE placed (d/pingan/new/npc/muren.lpc, d/shaolin/muren-xiang.lpc, d/mingjiao, d/shushan, d/laoshaolin), but all are multiple zones away from the start area and out of this pass's time budget to path to and verify live. Used 将军府's own in-game-documented alternative instead: 练武场's sandbags object (d/jjf/front_yard.lpc's do_da()) is a genuinely safe (no death risk, only kee cost) skill-training mechanic gated on sect membership and a combat_exp threshold — exercised live above, correctly rejected for a too-fresh character. Not claiming the muren mechanism itself was verified live — flagging this honestly per §10.7 item 6 rather than presenting it as tested.

Bugs found and fixed

1. §7.34 (leftover developer debug output) — adm/daemons/logind.lpc get_name(). Live-reproduced on the very first registration attempt: between the Chinese-name prompt and the admin-password prompt, the raw transcript showed 您的中文名字:/obj/login#150 — the login object's driver-assigned path printed instead of moving straight to the next prompt, byte-identical in shape to the xianlvqiyuan/esI instances already cataloged in §7.34. Root cause: a bare printf("%O\n", ob); at line 775, right before ob->set("name", arg);, with no explanatory comment — a debug checkpoint the original author never removed. Fix: deleted the line. Re-verified: re-ran the exact same registration flow (id qfshenmo and others below) — the raw byte sequence now goes straight from the Chinese-name prompt to the admin-password block with nothing in between.

2. NEW class — file_owner()'s fixed-depth sscanf misattributes any /u/<wizard>/<subdir>/... file, crashing log_error()'s log write on any compile warning under a nested wizard directory (matches AGENTS.md §7.26 exactly, independently found here). adm/simul_efun/object.lpc's file_owner() did sscanf(file, "/u/%s/%s/%s", dir, name, rest) == 3; return name; — returning the SECOND segment (a subdirectory name) instead of the wizard's own uid for any file nested more than one level under /u/. Live-reproduced: walking a fresh character north from 朱雀大街 into 十字街头 (/d/city/center.lpc) for the first time this boot lazily compiled /u/xdao/ljs/npc/ljstudi.lpc (the 两界山土地 NPC placed there), which has an ordinary "Unused local variable" warning; log_error() then tried file_owner() → got back "ljs" (the subdirectory, not xdao) → user_path("ljs") → a directory that doesn't exist → [执行时段错误]: *Wrong permissions for opening file /u/ljs/log for append. "No such file or directory", caught by the driver but landing squarely inside center.lpc's own create()setup()reset()make_inventory() chain (the same first-visit-only shape as §7.17/§7.25). Confirmed a SECOND, byte- identical, dead (unreferenced-by-adm/obj/simul_efun.lpc) copy exists at adm/simul_efun/oo.lpc — left untouched per the project's leave-dead-code-alone convention, since it is not part of the compiled simul_efun and fixing it would be pure churn. Fix: capture only the first segment after /u/ (sscanf(file, "/u/%s/%s", name, rest) == 2), matching domain_file()'s own first-segment-only discipline immediately below it in the same file. Re-verified live: restarted the driver, walked the same west,north route into center.lpc on a fresh boot (forcing the same first-ever lazy compile of ljstudi.lpc) — zero Wrong permissions/ 执行时段错误 lines in debug.log this time, room populated normally.

3. §7.12 (2-arg tell_room() wrapper bug), severity-escalation shape — confirmed live, including the follow-on driver segfault. adm/simul_efun/message.lpc's tell_room(mixed ob, string str, object *exclude) called message("tell_room", str, ob, exclude) — with exclude an unset (int 0) local for any of this lib's 2000+ 2-arg call sites. **Contrary to this same lib's own earlier NOTES entry ("§15s"), which checked ONLY the C++ f_message() *body*'s switch on the 4th argument (whose default: branch does silently tolerate a bare 0 with no bad_argument() call) — the ACTUAL live crash comes from a layer that check missed entirely: this driver's efun prototype (src/packages/core/core.spec: void message(mixed, mixed, string | string* | object | object*, void | object | object*);) declares the 4th parameter as void | object | object* — NOT int/mixed — and the driver's own prototype-based argument type-checker rejects a literal int 0 at the call boundary, before the C++ body's tolerant switch is ever reached.** (Correcting that earlier conclusion here rather than silently overwriting it, since it's a genuinely instructive miss — the efun's declared *prototype* and its C++ *implementation* can disagree, and checking only the implementation body isn't sufficient.)

Live-reproduced via obj/user.lpc's user_dump() — the NET_DEAD_TIMEOUT-driven (600s / 10 real minutes) force-quit handler, same function class as dtsl's original find: 1. Deliberately net-deaded qfengda (disconnected without quit), then did ONE blocking sleep 660 (past the 600s timeout) before reconnecting. 2. debug.log showed exactly the predicted crash: [执行时段错误]: *Bad argument 4 to EFUN message() Expected: object, array, Got: int(0). with a backtrace rooted at user_dump() /obj/user.lpc 181行tell_room() — i.e. the very tell_room(environment(), name + "断线超过...分钟,自动退出这个世界。\n") call in user_dump()'s DUMP_NET_DEAD case. Per §7.12's own documented mechanism, this uncaught crash (no enclosing catch(), fired from a bare call_out) aborted the REST of that switch case — the very next line, QUIT_CMD->main(this_object(), "", 1);, never ran, so the net-dead force-quit safety net was silently disabled, exactly as on dtsl. 3. The follow-on driver crash also reproduced: the very next reconnect attempt against that half-handled net-dead object hit CONNECT_FAILED: [Errno 111] Connection refused — the driver process itself had died. Captured stdout showed a genuine C-level Segmentation fault (Address not mapped to object [0x65]) inside apply_low(), dereferencing a corrupted ob->shadowed pointer, with command_hook()f__call_other() on the stack — the exact double-free/dangling-object shape §7.12's dtsl writeup and §10.8's catalog already describe.

Fix: exclude || ({}) at the message() call site inside the tell_room() wrapper — the same one-line, project-standard fix used on every other lib carrying this bug class. Re-verified live end-to-end, twice:

Net-dead / reconnect / soak testing (§10.7 items 8–9)

Shop purchase / combat (§10.7 item 6/10)

Observations (documented, not fixed — genuinely unresolved)

Files modified this pass

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

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

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

1. adm/simul_efun/file.lpclog_file() 没有 assure_file() 目录预建保护;补上调用及前向声明。cat() 补上 read_file() || "" 空值防护。(simul_efun1/simul_efun2/ 下的重复副本未被 adm/obj/simul_efun.lpc #include,确认是死代码,未处理。) 2. adm/daemons/closed.lpc(AGENTS.md §7.107,本轮在 nt1 发现的 同一类 bug——独立确认的第二条血统)load_all_users() 的两处 restore() 调用同样没有 catch() 保护,结构与 nt1 那份逐字节 一致。本档案的 data/closed.o 目前 closed_users 记录很少(几乎 为空),所以严重程度远不如 nt1(后者约 150 个账号),但同一漏 洞成立——按 §7.107 记录的写法预防性修复(catch(ok = X->restore()) + err || !ok 判定),未等真的出现损坏账号触发才修。

cmds/wiz/update.lpc(§7.106)与 master.lpc::log_error()(§7.10 的 "arning:" 大小写无关写法)均已是正确写法,无需改动。

config.fluffosmaximum evaluation cost 已经是 2000000000,远 超本项目安全值,无需调整。

现场验证

驱动干净启动,以管理员账号 fluffos/Mud@2026 登录,确认 您的系统权限目前是:(admin),落地"南城客栈"(既有存档正确持久 化)。update /adm/daemons/logind 成功("重新编译 /adm/daemons/logind.lpc ...成功!"),确认两处修复编译干净。两次快 速重连均正确显示 (admin) 权限、连线次数正确递增(第八次、第九 次),quit 均干净。debug.log 全程检查(1175 行):无真实错误, 仅无害的宏定义匹配。

(登录流程记录一处方法论细节,非 bug:本档案开场"① 进入(Enter) ② 离开(Exit)"菜单的 if_young() 处理函式实际检查 arg[0..0] == "1",纯空字串确认不会推进——尽管提示文字写着"(Enter)",脚本化测试 时需要真的送出字元 "1",而不是空白 Enter,才能进入下一步。)

本轮修改的文件

§7.100 sweep (2026-08-19): redundant replace_program(ROOM); landmine

Same corpus-wide bug as jhfy3's §7.100 finding (AGENTS.md): every room inheriting ROOM (/std/room) followed its inherit ROOM; with a redundant, harmful replace_program(ROOM); call in create(), setting a permanent "pending replace" flag that crashes the object the first time anything later binds a closure to it. This lib had 1,957 live occurrences (survey-ranked #81 of 166 candidates >=100). Fixed with the sweep's binary-mode script (fix_710_room.py) plus 8 hand-fixed room-building-tool copies sharing the same bug baked into their own code-generation templates (obj/roommaker.lpc, sjsh/obj/roommaker.lpc, sjsh/u/calvin/obj/roommaker.lpc, u/calvin/obj/roommaker.lpc, u/mery/obj/roommaker.lpc — simple str += "...replace_program(ROOM);..." variant; sjsh/u/koker/obj/teshu/roommaker.lpc, sjsh/u/qkl/roommaker.lpc, u/koker/obj/teshu/roommaker.lpc, u/qkl/roommaker.lpcroom_code- prefixed 3-occurrence variant). Note: u/calvin/obj/roommaker.lpc and sjsh/u/calvin/obj/roommaker.lpc are two SEPARATE, non-symlinked files (the lib's sjsh/ orphaned duplicate snapshot noted above) — both needed the fix independently, easy to miss one if only grepping the sjsh/ prefix. git diff --numstat totals (17 insertions, 1957 deletions) match the survey's live-occurrence count exactly. Verified via a clean build-debug boot (zero "cannot replace"/"cannot bind" debug.log lines, port 40067 listening) plus a live admin login (fluffos/Mud@2026) — look at 南城客栈 and quit both worked normally. No work/data/ false-negative room source found for this lib (only save .o files and unrelated content under data/). Incidental fluffos.o save-timestamp drift from the spot-check reverted before committing.

§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 sweep (2026-09-01): enable_player() reentrancy from init()

Same corpus-wide bug class as mhxy/wuhanzhan (AGENTS.md §7.19): this lib's feature/command.lpc enable_player() wrapper (around the raw enable_commands() efun) is reachable from an NPC's init() via a redundant create()-then-init()-calls-setup() (or reset_me() calling setup()) chain -- confirmed live via a static scan of every init() body in this lib: 41 NPC/item files call setup() directly or via reset_me() from init(), after create() already called setup() once (which already made the object living()). Calling enable_commands() a second time on an already-living() object makes the driver re-invoke that object's own init() as a side effect, which re-enters this same chain while the original call is still on the stack -- genuine reentrancy, crashing with "Too deep recursion" (most likely to surface on an NPC's first-ever preload/compile).

feature/damage.lpc's revive() and cmds/std/sleep.lpc's wakeup1()/wakeup2() all call me->enable_player() again while the object is still living() (kept alive across the disabled interval by disable_player()'s own internal re-enable_commands()). This confirms a bare if (living(this_object())) return; guard would be the WRONG fix (it would silently break that legitimate re-enable) -- used the same true reentrancy-flag fix as mhxy instead: a nosave private int in_enable_player_now; set for the duration of the wrapper's body, guarding only genuine same-call-stack reentrancy while leaving every legitimate re-enable (revive/wakeup/disguise) unaffected. feature/command.lpc's enable_player() had a single fall-through exit (no early returns), so one guard-at-top + one clear-at-bottom pair was sufficient. Verified via a single-file lpcc --batch compile check (PASS) -- not individually live-boot-tested.