Ultimate Hell: Legend of Xuanyuan

✅ 可玩

终极地狱之轩辕传说

zhongjidiyu

🔑 fluffos / Mud@2026 更新 66a3bd8 2026-10-28 源码 下载 ZIP

▶ 开始游玩 · Play Now

MUD 内部标识为 `hell.local`,属于 ES II 引擎系列,与 `es1_win`/`xkx2001`/`rzrmud` 等同属一支,master 文件署名一路写明 "for ES II mudlib" → "Annihilator" → "Xiang for XKX" → "Doing Lu for hell",是一款以"轩辕传说"为主题的武侠/仙侠向 MUD。压缩包自带的说明文件明确写着"这是供学习研究用的底层源码,不建议直接拿来架设正式对外服务器"——这份存档本身是一个"骨架版",只保留了新手村、死亡结算等少数区域,长安城、武当、少林等大部分门派/城市场景并未包含在这个压缩包里;新角色刚进游戏时属于"未出生"状态,需要先找水笙姑娘用 `register <邮箱>` 注册邮箱,再走一遍"投胎做人"流程选定资质属性才能正式踏入江湖。本项目里还有另外两个名字也带"终极地狱"字样的压缩包(`zjdyaryl`、`zjdyzj`),那两个属于"地狱"分支下的另一条独立血脉,与本作并非同一款游戏,三者内容互不相同,只是碰巧同名。

English

A Xuanyuan-legend-themed wuxia/xianxia MUD, internally identified as hell.local and part of the ES II engine family alongside es1_win/xkx2001/rzrmud in this collection, its master file's authorship trail running through 'for ES II mudlib' to 'Annihilator', 'Xiang for XKX', and 'Doing Lu for hell'. The archive's own bundled notes describe it as a stripped-down reference build 'for study purposes, not recommended for a public server' — only the newbie village and death-handling rooms are included, with the major sect/city zones (Chang'an, Wudang, Shaolin, etc.) referenced in code but absent from this package. New characters start 'unborn': they must first find the NPC Shuisheng to register an email with the register command, then go through a stat-rolling 'reincarnation' (投胎做人) ritual before actually entering the world. Two other archives in this collection also happen to carry 'Ultimate Hell' in their name (zjdyaryl and zjdyzj) — those belong to a separate branch of the 'Hell' lineage and are different games entirely, sharing only the coincidental title; all three have otherwise unrelated content.

README

内容亮点

在线试玩

https://mudlibs.fluffos.info/zhongjidiyu/

管理员账号 / Admin account

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

本地运行

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

游戏端口:40072。

NOTES · 移植与修复记录

zhongjidiyu — 终极地狱之轩辕传说 (Ultimate Hell: The Legend of Xuanyuan)

Archive: 终极地狱.rar (archive #78 of ~100). Port: 40072. Status: done (boots clean, full registration flow verified with two real Chinese names in separate continuous connections, post-login commands look/score/quit all confirmed producing correct real output).

What this is

Self-extracts to a nested revive/ root (not top-level) containing a raw readme (说明文件.txt, GBK-encoded, filename itself needed a manual re-encode pass after extraction — see "Extraction gotcha" below) that reads (translated): "This is the bottom-layer source code released by doing... released for study purposes, not for running a live server with." Live banner reads "终极地狱之轩辕传说" ("Ultimate Hell: The Legend of Xuanyuan"), MUD_NAME in the raw config is hell.local. adm/single/master.c's own header credits the full lineage: for ES II mudlib / original from Lil / rewritten by Annihilator (11/07/94) / modified by Xiang for XKX (12/15/95) / updated by Doing Lu for hell (2K) — this is the same "ES II" lineage already seen across ~20 prior libs (es1_win/esI/xkx2001/rzrmud/xo/ bmxkx2001/kxkj/yueyingqiyuan/wuhanzhan/haiyang2/ yanhuangwuhun/yhyxs/xuanjianlu/shenzhou/shenmo/ kxkj1/etc.), specifically closest in file layout (adm/single/{master,simul_efun}.c, adm/daemons/, adm/simul_efun/) to xkx2001(#25)/rzrmud(#23)/bmxkx2001(#45). securityd.c's own header credits "[email protected]" then "Modified by Xiang for XKX" then "Updated by DOing Lu for ULTRA HELL" — same chain. MudOS v22pre11-era target.

IMPORTANT — cross-check flagged for the main session: two OTHER archives sharing the "终极地狱" name are being processed concurrently by sibling agents in this same batch: archive #79 (终极地狱之爱若幽兰1.166正式版.rar) and archive #80 (终极地狱-指间mud版服务端.rar). This agent could not cross-check against them (concurrent extraction). The main session should diff/md5sum core files (master.c/chinese.c/securityd.c/logind.c) across all three once all are done to determine whether they're the same game at different snapshots, forks of one lineage, or unrelated codebases that happen to share a title — per this project's repeated lesson (shujian2008 vs sjpl2, xianlvqiyuan vs xlqy_new2007, etc.) that similar Chinese titles are NOT a reliable lineage signal on their own.

Extraction gotcha

The archive's own readme file extracted with a GBK-encoded filename that unrar's locale handling left as raw undecodable bytes (˵\udcc3\udcf7\udcceļ\udcfe.txt — Python surrogate-escaped). Renamed it to its correct decoded name (说明文件.txt) via a small Python os.fsencode/gb18030-decode script rather than leaving the mojibake filename in work/.

Fixes applied (proactive, per AGENTS.md catalog, before first boot)

New/notable bugs found (not yet in AGENTS.md's numbered catalog)

1. virtuald.lpc's "virtual object" fallback treats file_size() == -1 (nonexistent) as truthy "file exists" — compile_object(file) walks file's path components and does if (file_size(name + ".lpc")) { ob = name->query_virtual_room(...); }. Since file_size() returns -1 for a missing path (a nonzero int, so truthy), this ALWAYS "succeeds" into calling name->query_virtual_room() for a name that doesn't exist as an object either — which triggers the driver to lazily load_object(name), which fails (file doesn't exist), which calls master::compile_object(name) AGAIN as the driver's standard "couldn't find this file, try a virtual object" fallback, which calls virtuald.compile_object(name) again... recursing until Inherit chain too deep: > 30 aborts it. This isn't cosmetic: it fired continuously (every NPC-patrol heartbeat tick referencing a room in the missing /d/city//d/city3 zones — see gap #2 below) and, before it was fixed, appeared to be eating enough eval-cost/CPU each tick that the FIRST test session's character landed with a corrupted room state (fell back to START_ROOM which itself doesn't exist either, see next item). Fix: file_size(name + ".lpc") >= 0 (a real, existing file) instead of a bare truthy check. This is a genuine, driver-lineage-wide engine bug independent of missing content — worth checking any other ES-II-family lib with a virtuald.lpc/"virtual object" fallback for the same bare-truthy-file_size() shape. 2. Missing zone content, confirmed real archive gap (§13): /d contains ONLY death/, pk/, register/ — every city/sect zone (/d/city, /d/city3, /d/wudang, /d/xuedao, /d/beijing, /d/shaolin, /d/xingxiu, /d/heimuya, dozens more) referenced by START_ROOM (/d/city/kedian), cmds/chat/enterworld.lpc, npcd.lpc's patrol tables, mapd.lpc's map data, and 48 clone/board/*_b.lpc bulletin boards is genuinely absent — this really is the "bottom-layer source code... for study" skeleton release its own readme describes, not a full world pack. Also affects a whole sub-feature: /data/room/moman/* (a "house" template set, 绿竹苑) that #includes /d/room/roomnpc/roomnpc.h (also missing, defines is_owner()/owner_is_present()) — 9 files affected, all under the same missing /d/room/ tree. Because START_ROOM itself (/d/city/kedian) doesn't exist, logind.lpc's own fallback-to- START_ROOM path is dead code that always fails too — but this is masked in practice because REGISTER_ROOM (/d/register/entry, which DOES exist, home to NPCs 水笙/狄云) is what a genuinely fresh (! registered) player actually uses, so the missing /d/city never surfaces during ordinary registration. Documented, not fabricated. 3. d/register/npc/diyun.lpc unguarded factory-chain call (§15e): carry_object("/d/city/obj/cloth.lpc")->wear() in this NPC's create() — /d/city/obj/cloth.lpc is part of the missing /d/city zone (gap #2), so carry_object() correctly returns 0, and the unguarded ->wear() on it threw Bad argument 1 to EFUN call_other() every time this NPC's create() ran (i.e. every time /d/register/entry, the REAL register room, reset) — which, because the error was inside a catch(load_object(startroom)) in logind.lpc, made the ENTIRE register-room load look like it failed, sending every new character to the (also nonexistent) START_ROOM fallback instead of the real, populated register room. This was the single highest-impact fix in this pass — before it, characters landed with a broken/empty environment and look/score appeared to produce no output at all. Fixed with the standard if (objectp(cloth)) cloth->wear(); guard; the NPC simply goes unclothed (acceptable, matches precedent). 4. Genuinely broken/orphaned, never-loaded daemon files (not fixed, not reachable in normal play — same category as §12's "orphaned non-LPC file", just for an incomplete .lpc instead): adm/daemons/ cruised.lpc ("漫游精灵", cross-server player roaming) uses vfd/ socket_info without ever declaring them, references STREAM_BINARY without #include <net/socket.h>, and has a CAHNNEL_D typo (for CHANNEL_D) — none of CRUISE_D is even #defined anywhere, and nothing else in the lib references it; not in preload. adm/daemons/ quest/girl.lpc similarly references undeclared rcv_npcs/ supply_objs globals and is never loaded by anything else either. Both read as genuinely incomplete/abandoned files from the original author, consistent with the archive's own "study source, not a full release" disclaimer. 5. Pre-existing typos, fixed (unrelated to encoding/rename, confirmed via raw-byte diff against the original archive): - inherit/condition/damo.lpc:225: if (! intp(cnd["level"]) || ! intp(cnd["remain"] || ! stringp(cnd["id"]))) — a misplaced closing paren (should close after cnd["remain"], not after cnd["id"]). - Same file, a genuine corrupted byte in the RAW archive (not our conversion): a stray invalid 0xf7 byte immediately before a closing quote at raw byte offset 7414 (inside "发作身亡了") made the whole file fail strict GB18030 decoding, so convert_lib.sh fell back to iconv -c (lossy), whose "eats an adjacent real byte" recovery heuristic then corrupted a LATER, unrelated string literal ("长长的吁" @ line 278) into 5 invalid UTF-8 bytes — the exact xo_final/tianxia pattern documented in AGENTS.md's Encoding section. Fixed by stripping the single stray byte from the RAW bytes directly (confirmed via bytes.decode('gb18030') raising UnicodeDecodeError at that exact offset) and re-decoding the whole file cleanly, rather than patching the already-corrupted UTF-8 output. - adm/simul_efun/util.lpc never #include <ansi.h> despite using NOR/HIY/HIW/HIR/HIG/HIC/WHT/BCYN throughout — silent in a real boot only because simul_efun.lpc (which #includes this file) already pulls in ansi.h itself first (§6b: works fine composed, fails standalone under lpcc). Added the #include directly for correctness/self-sufficiency (harmless, header-guarded). - inherit/misc/quest.lpc's set_information(string key, string info) — narrower than QUEST_D's own mixed info, and 5 quest template files (clone/quest/{explore,judge,search,shen,supply}.lpc) pass a function-pointer closure ((: ask_npc :)), not a string — widened to mixed info (matches AGENTS.md's already-cataloged "quest set_information signature" bug class, e.g. yhyxs).

Confirmed NOT needed (checked, not present)

Testing methodology note (worth flagging for future libs)

This lib sets user->set("env/prompt", "time") for every new player — a live, once-per-second clock prompt re-drawn from the character's own heart_beat() (inherit/char/char.lpc), independent of the driver's write_prompt()/process_input() applies. scripts/mudclient.py's --idle pacing logic resets its "quiet" timer on ANY received byte, including these once-a-second heartbeat prompt redraws — with the default/ larger --idle values used successfully on most other libs (1.0–3.5s), the heartbeat's constant ~1s drip meant the "idle" condition was never satisfied, so queued --send lines for look/score/quit were silently never actually transmitted at all (confirmed via -dconnections driver debug output: zero get_user_command() calls for those lines). This looked EXACTLY like the §15ae "commands silently do nothing" failure mode and cost significant diagnostic time (traced through add_action, exec()/interactive-transfer, process_input()/APPLY_PROCESS_INPUT, even a raw byte-level socket script) before the actual cause — a test-tool pacing artifact specific to libs with a persistent per-second heartbeat prompt, not a mudlib bug — was found. Fix for testing: use a SHORT --idle (0.3–0.5s, shorter than the heartbeat period) on any lib with a live/clock-style env/prompt, or use a raw socket script that sends after a fixed wait rather than an idle-detection loop. Worth promoting to AGENTS.md as its own catalog entry for the next lib with a similar live-prompt feature.

Registration flow shape

get_id (English id, 3-10 letters) → confirm_id (y/n, only for a NEW id) → get_surname (Chinese, ≤2 chars, optional/blank-skippable) → get_name (Chinese, ≤2 chars, combined surname+name must be ≥2 chars, checked against NAME_D->invalid_new_name() for banned Jin Yong character names and near-duplicate existing names) → new_ad_password/ confirm_ad_password (a SEPARATE "admin/recovery password", ≥5 chars) → new_password/confirm_password (the regular login password, ≥5 chars) → get_type (1-5, character "build" archetype, blank defaults to 5/balanced) → get_gender (m/f) → enter_world(). No hidden BIG5/GB prompt, no client-version gate, no anti-flood throttle found in band.lpc (only is_banned(site) — a real IP-ban list, not a per-connection registration throttle).

Interactive test result

Full registration + post-login flow verified in two separate continuous connections with real Chinese names "秦风" (male) and "林枫" (female, type 耐力型), both reaching the actual populated starting room "世外桃源" (with NPCs 水笙/狄云 present, exits west/east/north/south). Both sessions then sent look (re-displayed the correct room description), score (correctly responded "还没有出生呐,察看什么?" — a legitimate game-rule message, since neither test character completed the additional register <email> + "投胎做人" reincarnation step the unregistered banner describes; this is real command execution, not a silent failure), and quit (correctly responded "欢迎下次再来!" and disconnected). Zero debug.log errors in the final clean runs beyond the confirmed-harmless versiond.lpc socket_bind cosmetic line. command_hook's private removal (§15ae) confirmed working via this real post-login-command test, per the project's standing policy.

lpcc sweep

1169 files, 1072 pass / 97 fail (91.7%). All 97 failures triaged into already-understood categories, none require further fixing:

Files touched (for reference)

adm/simul_efun/chinese.lpc, adm/daemons/logind.lpc, adm/daemons/ named.lpc, adm/daemons/securityd.lpc, adm/single/master.lpc, feature/command.lpc, adm/simul_efun/message.lpc, clone/user/user.lpc, adm/etc/preload, adm/daemons/virtuald.lpc, d/register/npc/diyun.lpc, adm/npc/luban.lpc, inherit/condition/damo.lpc, adm/simul_efun/ util.lpc, inherit/misc/quest.lpc, plus the ~30-file "nosave/ → "static/ revert (§3 counterexample) and directory creation (log/, log/static/, log/user/, binaries/).

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

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

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

WASM long-sit boot-watch pass (2026-07)

200s scripts/wasm_boot_watch.sh sit: the quest-daemon heart_beat() "Bad argument 1 to call_other"/"Bad argument 2 to present()" errors (clone/quest/judge.lpc, .../search.lpc, .../supply.lpc, .../shen.lpc) reappeared, matching the already-documented pre-existing "skeleton archive's missing zones" class (recorded above, unrelated to the VERSION_D guards) — confirmed still the same already-known class, no new fix needed. ftpd.lpc/versiond.lpc sockets-absent preload errors also match the already-documented §1.3c class (caught, cosmetic). One genuinely new proactive fix: this lib shares inherit/item/combined.lpc (private void destruct_me(), call_out-self-invoked when a money stack hits amount 0) and adm/simul_efun/object.lpc (file_owner() return name bug) with sibling zhonghua2, where both fired live during that lib's sit — ported both fixes here proactively (dropped private on destruct_me; file_owner() now return dir) even though neither triggered visibly in this lib's own 200s window. Retest: fresh registration (id zjdsanb) into 世外桃源, look/score(pre-投胎 message)/quit clean, no regressions.

深度功能测试 / Deep functional test (round two, per AGENTS.md §10.7)

Native driver only (~/src/fluffos/build-debug/src/driver config.fluffos from libs/zhongjidiyu/), scripts/mudclient.py --idle 0.5-0.6 throughout (this lib's live per-second clock prompt requires it, §15an/AGENTS.md §8.3 item 1 — reconfirmed still necessary). Read help/newbie and help/intro first, in full, before touching the client — they document the actual intended flow precisely: 水笙 register <email> → decide → walk to one of 世外桃源's 4 exits and out to pick a 性格/character trait → out again into 阎罗殿 (Hall of Yama) → wash (roll 4 innate stats) → born <地名> (choose birthplace, triggers reincarnation and moves the player to their starting zone). Safe sparring is the ordinary fight command ("较量" — per help/intro, stops before either side dies); skill learning is bai (apprentice) then xue (learn) from an NPC teacher; sect/faction join happens the same way, via bai-ing a sect NPC.

One continuous session, two test characters (both left in place as evidence): hufengbo/秦风 (male, 猛士型, admin password Admin@2026, regular password Mud@2026) and muyunqing/林枫 (female, 耐力型, same password scheme). Both completed real registration through register+decide+personality-room+wash end-to-end, confirmed via look/score/i at every step. Admin account fluffos/浮云 (seeded by an earlier pass) also used, with goto, to reach content a fresh player account cannot (see below).

Confirmed still correctly applied, not re-diagnosed as new:

Bug found #1 (NEW) — a failed born corrupts a real account into a permanently no-environment login, and enter_world()'s own fallback chain doesn't actually catch it. This is the highest-impact finding of this pass. Root cause, two parts, both fixed:

1. d/register/yanluodian.lpc's do_born() persisted startroom/born/born_family to the player's SAVE data unconditionally, BEFORE checking whether the destination object actually resolved (obj = load_object(dest)/find_object(dest)). Since every born destination in this skeleton archive points into a missing zone (/d/city, /d/beijing, etc. — none exist, see gap #2 above), literally every born attempt "fails" (prints "牛头一呆, 搔搔头说:怎么好像有问题...") — but the state was ALREADY written by then. This flips enter_world()'s own !stringp(user->query("born")) safety net (which would otherwise keep an unborn player parked safely in BORN_ROOM/阎罗殿 on every future login) to false, so it stops applying. 2. adm/daemons/logind.lpc's enter_world() then tries to resolve the now-persisted (broken) startroom. Its own fallback logic only wrapped catch(load_object(startroom)), not the subsequent user->move(startroom) — and load_object() on a path that simply doesn't exist (with no virtual-object fallback) returns 0 without throwing, so the catch() sees "no error" and falls into the UNGUARDED user->move(startroom). feature/move.lpc's move() does call_other(dest, "???") as its very first destination-resolution step, which genuinely throws (*call_other() couldn't find object '/d/city/kedian'.) for a nonexistent path — uncaught here, aborting enter_world() mid-function. Confirmed live: reconnecting as the already-"born" hufengbo printed "你无法进入这个世界,可能你的档案 出了一些问题" and then look returned "你的四周灰蒙蒙地一片,什么 也没有" — the exact §7.22 "no environment at all" symptom, but via a distinct mechanism (neither an eval-cost abort nor a compile error — a plain unguarded call_other() inside an un-catch()-wrapped move()). score still worked (doesn't need an environment), which is what made this state look survivable at a glance — look/movement were the ones silently dead. This lib's own hardcoded START_ROOM fallback (/d/city/kedian) is ALSO one of the missing paths, so even the "else" branch's intended recovery was reaching for a target that doesn't exist either.

Fix, two parts (both live-verified — see below): - d/register/yanluodian.lpc: moved the !objectp(obj) failure check to immediately after obj is computed, in BOTH the plain-string and the family/世家 branches, before any me->set(...) call. A failed born now leaves the account's startroom/born fields untouched, exactly as if born had never been attempted. - adm/daemons/logind.lpc's enter_world(): wrapped the actual user->move(startroom) in catch() too (not just load_object()), and added a THIRD fallback level — if even START_ROOM fails to load/move into (true on this specific archive), fall back to BORN_ROOM (阎罗殿, always present) instead of leaving the player with no environment at all.

Live verification: before the fix, reconnecting as hufengbo (who had already borned to a missing destination in a prior session) reproduced the crash exactly as described, with *call_other() couldn't find object '/d/city/kedian'. in debug.log. After the fix (driver restarted to pick it up), the SAME already-corrupted hufengbo account now lands in 阎罗殿 via the BORN_ROOM fallback (kicked onward to 虚空/void by 地藏王's own already-correct "you're already born, get out" NPC logic — a real, pre-existing, working game mechanic, not a bug), and look/score/movement/quit all work normally from there — no more no-environment state. Separately, a FRESH character (muyunqing) that ran wash+born <missing dest> AFTER the fix correctly stayed in the "not yet born" state (score → "还没有出生呐,察看什么?", still standing in 阎罗殿 with 地藏王 still willing to hand over the newbie book) on both the immediate next command and a later real reconnect — confirming the root-cause fix (part 1) prevents the corruption from happening at all for any new account, while the defense-in-depth fix (part 2) also protects any account that manages to reach a broken startroom by some other path. No new debug.log errors from any of this.

Doesn't match any existing catalog entry exactly — closest are §7.14 ("missing post-registration destination room... guard the move with load_object()/fallback to START_ROOM") and §7.22 (the "no environment at all" symptom shape) but the specific mechanism (a catch() that only wraps the probe call, not the actual unguarded move() right after it, PLUS a state-mutation-before-validation ordering bug in the caller) is new. Worth a full write-up as a candidate new §7.x entry — see final report.

Bug found #2 (matches AGENTS.md §8.3a's addendum exactly, not new) — feature/action.lpc's eval_function() was still private. Found live: apply() with insufficient permission: ... function: eval_function, origin: internal, needs: private, has: hidden fired in debug.log during ordinary play (a delayed-effect call_out against our own test character's body). This is the SAME file and SAME function name as the xuanjianlu-derived precedent already in §8.3a's addendum ("the shared 'delayed status effect' primitive used by 130+ kungfu-skill/drug files"). The lib's own "WASM long-sit boot-watch pass" note (above) proactively ported §8.3a's addendum fix for inherit/item/combined.lpc's destruct_me() and adm/simul_efun/object.lpc's file_owner() from sibling zhonghua2, but missed this THIRD instance — feature/action.lpc itself apparently wasn't part of that proactive sweep. Fixed identically: dropped private, kept the function otherwise unchanged. Proactively grepped the whole lib (every call_out("name", ...) site cross-referenced against a same-file private declaration of name) for any other instance — found none: every other flagged hit is a call_out target defined in a standalone daemon/NPC/command file that is never inherited elsewhere (confirmed via grep -rl 'inherit.*"<path>"'), so the DECL_PRIVATE→DECL_HIDDEN-once-inherited mechanism doesn't apply to them.

Bug found #3 (matches AGENTS.md §7.25 exactly, not new, but a genuinely new instance/location) — inherit/room/room.lpc's shared make_inventory() had no guard, crashing the ENTIRE afterlife zone's first-ever visit. d/death/npc/wgargoyle.lpc ("白无常") and d/death/npc/bgargoyle.lpc ("黑无常") both call set_skill("dodge", ...) in their own create() — and this archive's /kungfu/skill/ directory is entirely absent (not just the /d/city-style zone rooms already documented in gap #2 — the base skill-file tree itself is missing, a broader gap than previously written up). feature/skill.lpc's set_skill() correctly error()s on a missing skill file, but that error() fires DURING the NPC's create(), which new() propagates as an uncaught throw — and inherit/room/room.lpc's make_inventory() (ob = new(file); ob->set(...); ob->move(...); return ob;, called from every room's reset(), lib-wide) had zero catch() around any of it. Live-reproduced via admin goto /d/death/gate (DEATH_ROOM, where every player death sends them via feature/damage.lpc's die()): the room's FIRST-EVER reset() this boot aborted entirely, so goto silently failed to move the admin there at all (landed back at start with only a debug.log trace: *F_SKILL: No such skill (dodge), blamed at wgargoyle.lpc's create() via room.lpc's make_inventory()). Same shape independently confirmed for bgargoyle.lpc in /d/death/gateway.

Fix: wrapped make_inventory()'s new()/set()/move() chain in catch(), returning 0 on any failure (destructing a partially-constructed object first if new() itself did return one); and guarded the ONE call site in reset()'s case 1: branch that dereferenced the result without an objectp() check (ob[list[i]]->is_character()) — the default: (multi-object) branch already continued past the equivalent dereference for a freshly-(re)- made entry, so it didn't need the same guard. Live-verified this unblocked the ENTIRE death/afterlife zone, not just the one room: after the fix, admin goto successfully reached and looked at all 8 death-zone rooms (gate/鬼门关, road1/road2/road3/鬼门大道, inn1/小店, inn2/黑店, god1/天堂, god2/圣殿, block/空房间, gateway/酆都城门) with zero further errors beyond the two now-gracefully-caught missing-skill NPCs — this whole zone is otherwise 100% present and intact in the archive, just unreachable-on-first-visit before this fix. Matches §7.25's established pattern and fix precisely; the missing /kungfu/skill/ tree itself is left alone as a genuine archive content gap (not fabricated) — 白无常/黑无常 simply never spawn, same graceful-degradation outcome §7.25 already prescribes for this shape.

Confirmed content gap, broader than previously documented: the /kungfu/skill/ directory (referenced by include/globals.h's SKILL_D(x) macro, "/kungfu/skill/" + x) does not exist AT ALL in this archive — not one basic skill file (dodge, force, unarmed, etc., all named in help/newbie's own example cha output) is present, beyond the previously-documented missing /d/city-style zone rooms. This means even IF a player could reach a populated zone, xue/learn-style skill acquisition would have nothing to actually load. Documented here, not fabricated — matches the archive's own "study source, skeleton release" disclaimer.

Safe-sparring / skill-teacher / sect-join — confirmed NOT reachable live, by design of this skeleton archive, not a bug. Every currently-existing room in the archive (d/register/*, d/pk/*, d/death/*) explicitly sets no_fight: 1 — the fight command (§ help/intro's "较量") is real, present, and correctly wired (cmds/std/fight.lpc exists and is a normal add_action command), but there is no reachable room anywhere in this archive where combat is actually enabled. The bai(apprentice)/xue (learn) skill-teacher path and every sect-join NPC live under the missing /d/<sect> zones (§13/gap #2) and, per the finding above, even the underlying skill FILES they'd teach don't exist either. d/pk/ ("屠人场" — a PK tournament arena, 乌老大's sign/join commands) is a distinct, unrelated feature (scheduled PvP, not the newbie-doc's safe fight spar) and is itself a dead end too (west exit → /d/changan/..., missing). Consistent with, and extends, this lib's own already-documented "skeleton archive" gap — not attempted to fabricate.

Shop purchase / combat / death — explicitly NOT tested live, and cannot be, given the above. No shop, no fightable NPC, and no death trigger is reachable by an ordinary player account in this archive's current content. Code review only: feature/damage.lpc's die() calls me->move(DEATH_ROOM) (/d/death/gate, confirmed to exist and — after Bug #3's fix — load cleanly) and DEATH_ROOM->start_death(me); this path was not exercised by an actual player death since no live combat path exists to trigger it.

quit + debug.log grep, both unclean (net-dead) and clean-quit reconnect, both prompt and after a real wall-clock gap: all tested, all clean.

Files touched, this pass

WASM 修复摘要(迁移自 meta.json 的 group_note)

尽管标题相似,但和 hell 那对"终极地狱"没有关系。状态已从过时的 limited 修正——这份档案自己的 README 和 group_note 里从未记录过任何缺陷说明,本轮重新测试也没有发现:管理员登录(fluffos/Mud@2026)干净正常,'目前权限:(admin)'。另外还发现并修复了一个和手足档案 zjdyaryl/zjdyzj 共有的真实 bug:clone/user/login.lpc 的 query_save_file() 只防范了 id 不是字符串的情况(!stringp(id)),没有防范空字符串的情况——对 "" 取 id[0] 会回传整数 0,而 sprintf 的 %c 格式会拒绝这个值('Incorrect argument to type %c, must be valid UTF8 char'),在任何 id 被输入之前,第一次自动呼叫 query_save_file() 就会崩溃。已通过额外检查 id == "" 修复。

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

§7.100 sub-threshold instance (2026-08-20)

Found during the §7.100 tail-sweep (below the original 166-lib survey's

=100-occurrence threshold, never checked). 39 live

replace_program(ROOM); occurrences across 38 files: 18 clone/megazine/*.lpc rooms, clone/misc/void.lpc, a 12-file d/pk/turen*.lpc cluster, a 6-file d/death/*.lpc cluster, plus this lib's own room-building tool clone/misc/roommaker.lpc (both a standalone line and a separate string-builder template line, both fixed) — so newly-built rooms were inheriting the bug too. A full post-fix grep confirms 0 live occurrences remain. Verified via a clean native driver boot (zero new debug.log errors, port listening, killed by exact PID after ~8s).

§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): 5 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.

Shop + 拜师 (2026-09-04 librarian loop) — cheap-confirm, no re-boot

The existing deep-test body already records, with concrete evidence, that this archive is a skeleton: every shipped room is no_fight: 1, shop/sect NPCs and skill files live under missing /d/<sect> trees, and "No shop, no fightable NPC" was an explicit live-test conclusion rather than a heading-only TODO. This pass re-read that section and did not re-boot. Still no reachable shop or bai path to exercise.

深度功能测试(§10.7,2026-10-04)— negative range ends

scripts/lpc_fix_negative_ranges.py: 4 line(s) in 1 file(s) count a range from the end with < (x[a..-1] -> x[a..<1], x[-2..] -> x[<2..]; KB 06 §7.209). In this driver a negative constant end gave "" / ({ }) and a negative start the whole value, so each of these returned the wrong slice; the compile warning A negative constant as the second element of arr[x..y] is gone from the files that compile. Range lvalues (s[0..-1] = text, the prepend idiom) are left alone. A paired compile of the changed files at HEAD and in the working tree showed no new error.

深度功能测试(§10.7,2026-10-06)— 与 zhonghua2 同源的三处编译警告

验证:改动的文件和包含 npc/indian 的守护进程在 HEAD 与工作树分别加载(scripts/lpc_listcheck.sh,新进程),无回退。

深度功能测试(§10.7,2026-10-07)— 编译警告整理

scripts/lpc_warnings.py zhongjidiyu --fix 加 scripts/lpc_fix_no_effect.py --apply,再用 scripts/lpc_nitan_family_fixes.py zhongjidiyu(adm/daemons/natured.lpc 的空语句 lt[LT_MON];、adm/daemons/combatd.lpc 的空语句 your_temp["guarding"];),共改动 216 个 .lpc 和若干 .h,都不改变行为。HEAD 与工作树分别加载(新进程):199 -> 199 通过,无回退。 未动:adm/daemons/cruised.lpc(跨站漫游守护进程)是编译不过的草稿(set、socket_info 未定义,CAHNNEL_D 拼错),没人加载。

启动时警告 85 -> 0;新进程登录正常。

编译警告收尾(2026-10-13)

scripts/lpc_twin_port.py adopt revive zhongjidiyu 搬来 revive 同日整理(cfd7e6a9897)的改动,与本库逐 token 相同,能编译的文件警告清零;改动的对象在 HEAD 与工作树各载入一次,regress 0。

find_player() 只认在世界里的身体(2026-10-15,KB 07 §8.26)

simul_efun 的 filter_player() 加上 environment(ob):注册中途断线留下的、不在任何房间的身体不再被当成玩家,两个以上时 find_player() 不会再报 The player(<id>) has existed in the world.(wllz3 实测过的问题)。本库若注册流程不会留下带玩家 uid 的身体,这一改动不改变行为。

NPC 带着本库没有的物品,载入失败(2026-10-15,KB 06 §7.73)

2 个文件、3 处在 create() 里 carry_object("<本库没有的文件>")->wear()(或 wield()、set_amount()):carry_object() 得 0,接着的调用报 Bad argument 1 to EFUN call_other(),NPC 根本载入不了,该有它的房间里就少了这个人。多是从别的系统搬来的 NPC 用了原系统的物品路径(最常见:/d/city/npc/obj/qunzi、/d/shaolin/obj/changjian、/d/city/obj/cloth)。每处改为 { object carried_ob = carry_object(...); if (carried_ob) carried_ob->wear(); }(scripts/lpc_guard_carry_object.py):NPC 照常出现,只是不带这件东西;文件补回来(或是虚拟对象)照样会带上。载入检查:改动的文件改前能载入 0 个,改后 1 个(1 个原来载入不了),没有退步;其余 1 个另有原因(多为 set_skill() 了本库没有的武功)。

set_skill() 遇到本库没有文件的武功不再抛错(2026-10-18,KB 06 §7.256)

feature/skill.lpc 里的 set_skill()、map_skill()、prepare_skill() 碰到没有武功文件的名字就 error("F_SKILL: No such skill (…)"),调用它们的 NPC create() 整个中断:NPC 载入不了,刷出它的房间也少一个人。本库目前没有对象设了缺失的武功;三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。(scripts/lpc_guard_set_skill.py)

RANK_D 对 0 的称呼(2026-10-19,KB 06 §7.273)

adm/daemons/rankd.lpc 的 query_respect()、query_rude()、query_self()、query_self_rude() 里 4 个函数开头补上 if (!objectp(ob)) ob = this_object();。NPC 在 create() 里写 "…" + RANK_D->query_respect(this_player()) + "…"(inquiry 对话、chat_msg),驱动自己跑 reset() 时没有人在场,this_player() 是 0。旧驱动里 0->query("age") 返回 0,称呼走默认分支;新驱动直接报 Bad argument 1 to EFUN call_other(),房间的 make_inventory() 吞掉错误,被打死的 NPC 再也刷新不回来。用 scripts/lpc_reach_census.py --walk 找到(xkx100 的武当弟子、一灯、汴梁书生…);修在守护程序里,不动 1,800 个 NPC 文件。

member_array('/', str) 改为 strsrch(str, "/"):字符字面量的 member_array 按字节搜索(2026-10-20,KB 06 §7.283)

member_array('/', str) 里的 '/' 是整数,驱动按 UTF-8 字节搜索,返回字节偏移(带第三个参数时起点也是字节);最新的 FluffOS(923cdbc0)在字符串里有非 ASCII 文字时,对每个调用点警告一次(Warning: member_array(int, string) searches BYTES)。而 str[0..idx-1] 按字符切,中文字符串会在错误的位置截断。改为 strsrch(str, "/")(找不到同样返回 -1,按字符计数;scripts/lpc_fix_member_array_char.py,JSON 编码器等少数几处手改)。改动:/adm/daemons/vrm_server.lpc(1 处)、/cmds/arch/sameip.lpc(1 处)。

登录密码超过 255 字节:crypt() 抛错(2026-10-20,KB 06 §7.294)

驱动的 crypt() 对超过 255 字节的密钥直接报错(Error in crypt(), check your salt),老驱动则按任意长度散列。在登录提示符下输入 256 字节以上的密码,密码检查那一行就抛错,连线停在一个裸的 > 提示符上,登录走不完(password 指令的旧密码检查同样);用 300 个 x 当参数的扫描发现的。/adm/single/simul_efun.lpc 里补了一个同名的 simul_efun crypt(key, salt):只有超长的密钥才被截短(先截到 255 个字符,再逐字符截到 sizeof(to_buffer(key)) <= 255,限制按字节算,一个汉字占三个字节),再交给 efun::crypt();普通密码原样散列,已存的密码散列不受影响(scripts/lpc_add_sefun.py crypt)。

get_config(__MUD_PORT__) 经旧版 runtime_config.h 得到字符串(2026-10-20,KB 06 §7.302)

本库的 include/runtime_config.h 是老 MudOS 的编号(整数设置从 15 起,本驱动从 256 起),get_config(__MUD_PORT__) 读到的是另一项设置(字符串),(int) 又只是编译期标注,于是 socket_bind(fd, LOCAL_PORT()) 报 Bad argument 2 to socket_bind(),版本守护进程 port = get_config(__MUD_PORT__) + VERSION_PORT 是字符串相加。头文件自己的注释写明 get_config(__MUD_PORT__) == __PORT__,所以代码里的这个调用(连同外面的 (int) / to_int())改成了驱动的预定义 __PORT__(scripts/lpc_fix_mud_port.py)。改动:/adm/daemons/cruised.lpc(1 处)、/adm/daemons/v2.lpc(1 处)、/adm/daemons/versiond.lpc(1 处)、/include/getconfig.h(1 处)。

NPC 的 chat() 在 chat_msg 为空数组时下标越界(2026-10-21,KB 06 §7.334)

NPC 基类的 chat() 是 if (arrayp(msg = query(...))) { if (random(100) < chance) { rnd = random(sizeof(msg)); if (stringp(msg[rnd])) ...,不检查列表是否为空。本库现有的 NPC 文件里没有字面写成空 chat_msg 的(扫描只认 set("chat_msg", ({ })));守卫挡的是运行时才为空的列表和以后新增的 NPC。在 random(100) < chance 前面加 sizeof(msg) &&(scripts/lpc_fix_chat_empty.py;if (random(100) > chance) return 0; 的写法是 !sizeof(msg) ||)。改动:/inherit/char/npc.lpc(共 1 个文件)。

存档里的 .c 文件名改成 .lpc(2026-10-21,KB 03 §4.2 第 9 项)

存档里写着 "/d/city/npc/wei.c" 这样的文件名;extract 时源文件已经全部改成 .lpc,而驱动对带扩展名的路径只认字面,new() / load_object() / move() 都找不到它,调用方多半把失败当成「没有这个东西」悄悄跳过。本库 1 个存档共 1 处按 scripts/lpc_fix_save_dotc.py 改成 .lpc(只改 .lpc 存在而 .c 不存在的字符串;玩家存档、备份、留言板、巫师目录和文件索引缓存不动)。改动:/data/dbased.o(1 处)(共 1 个存档)。

注册到不存在函数的 add_action(2026-10-21,KB 06 §7.349)

add_action("函数", "动词") 绑定的函数,在对象本身、它包含的头文件、它继承的父类和继承它的子类里都没有定义(scripts/lpc_scan_callout_targets.py 静态解析,子类用传递闭包复核,scripts/lpc_verify_handlers.py 运行时 function_exists() 复核)。驱动从玩家最新的一条动作往回找,遇到这样的句子就报 *Function for verb 'drop' not found.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 4 条注册(3 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/adm/npc/referee.lpc(do_halt)、/clone/megazine/obj/maikaren.lpc(do_drop)、/inherit/misc/fboard.lpc(do_list)。最多的函数名:do_halt 2 处、do_drop 1 处、do_list 1 处。

不带前导斜杠的物品路径:缺失物品的 new() / carry_object() 调用链加了保护(2026-10-22,KB 06 §7.73 补充)

carry_object("d/city/obj/jiasha")->wear(); 这类路径不带前导斜杠的写法在这个驱动里按根目录解析(new("d/city/obj/changjian") 能加载 /d/city/obj/changjian),所以和带斜杠的一样:文件不在压缩包里时 new() 返回 0,->wear() 在 0 上报 Bad argument 1 to EFUN call_other(),整个 NPC 加载不了。此前的保护工具把这种写法当作「相对当前目录,没法判断」跳过了。1 个文件里 1 处(d/city/obj/cloth)改成带保护的块(scripts/lpc_guard_carry_object.py)。 对这 1 个改动文件做加载检查(HEAD 与工作区对比):HEAD 能加载 0 个,现在 0 个(新增可加载 0 个,回归 0 个)。

汉字按字节切片的老写法没有换成按字符:改法名 "澄" + name[2..3]、辈分判断 name[0..1] == "澄" 等 4 处(2026-10-22,KB 06 §7.360)

原档按字节算字符串区间(一个汉字 2 字节):name[2..3] 是名字的第二个字,name[0..1] == "澄" 问名字是不是以「澄」开头。转成 UTF-8 之后,这个驱动的区间和 strlen() 都按字符算(strlen("澄观") 是 2,"澄观"[0..1] 是两个字),老算术原样留着、意思全变:各派掌门的辈分判断(name[0..1] == "澄")永远不成立;NPC 的称呼和提示里取到两个字而不是一个字。

本库 4 处(2 个文件):与汉字常量比较 3 处、to_chinese()[..] 1 处。例:chk[0..1] == "你" → chk[0..0] == "你"(/adm/npc/ganjiang.lpc:542);to_chinese(martial)[4..7] → to_chinese(martial)[2..3](/cmds/skill/perform.lpc:119)。scripts/lpc_fix_gbk_slice.py 把下标按字符重算(社区的 UTF-8 版 xkx100utf8 里就是同样的写法,作对照);没有改的:截断(name = name[0..7],现在最多 8 个字,只是放宽)、ASCII 的区间(fname[0..3] == "room"、ctime(time())[4..19])和上下文看不出是汉字的区间。证明(scripts/lpcc_eval.py,bixiecanyang;其余各库是同一份文本):让澄字辈掌门 attempt_apprentice() 一个名叫「张三」的访客,HEAD 的法名是 慧,改后是 慧三;抽样 28 个库的 HEAD / 工作树加载对比 0 退步。工具不幂等([0..3] 折半得 [0..1],又是对齐区间),改过的文件记在 scripts/gbk_slice_converted.txt,不要重跑。

长度检查按字节写,旁边的提示按汉字写:2 处(2026-10-22,KB 06 §7.362)

老档的 strlen() 数字节(一个汉字 2 字节),转成 UTF-8 后它数字符:提示写「必须是 2 到 6 个中文字」的检查还是 strlen(name) < 4 || strlen(name) > 12,于是两三个字的房名、帮会名、群名、聊天室名、称号被提示自己说允许的那句话拒绝,上限却是提示的两倍;留言板的标题检查 i < 4 || i > 24 || i % 2 还会拒绝所有奇数个字的标题(post 你好吗)。本库 2 处(1 个文件):if ((strlen(name) < 4) || (strlen(name) > 12)) {(/adm/npc/luban.lpc:1764)。scripts/lpc_fix_strlen_gate.py 以提示为准:条件里每个 strlen 门槛都恰是提示所说数字的两倍才折半,存长度的变量(i = strlen(arg);)的门槛和 || i % 2 奇偶项、其后循环的 i % 2 == 0 && 一并处理;提示里有英文 / 字母 / 字节的不动。证明(scripts/lpcc_eval.py,bxsj 的留言板,求值对象继承 NPC 并重写 edit() 记录调用,command("post 标题")):HEAD 拒绝「你好」和「你好吗」、放行 23 个字的标题;改后接受前两个、拒绝第三个;shenmo 的 named.lpc invalid_new_name("张三"):HEAD 回 名字只能是2-4个中文字。,改后回 0。抽样 24 + 15 个库的 HEAD / 工作树加载对比 0 退步。

按字节写的汉字循环、茶馆猜诗和同盟频道标题(2026-10-23,KB 06 §7.363)

老档按字节做「一个汉字」:while (i--) if (i % 2 == 0 && !is_chinese(name[i..<0])) 只检查偶数下标,奇数位上的拉丁字母(张a)会过;茶馆猜诗 mixup() 用 strlen(str)/2 再 j=j+j、str[k..k+1] 交换两字节;泥潭系 channeld 的 switch (strlen(fname)) case 4/6/8/10 和 strlen(family)==4/6 按字节长度给同盟 / 门派名加空格。本驱动的 strlen() 和区间都按字符算。scripts/lpc_fix_byte_char.py:去掉奇偶项、把区间改成 name[i..i],按字符重写 mixup() 并把 strlen(first)>=14 折成 7,频道标题的 case / 门派长度改成字符数(对照 aoxiangtianji)。本库 channeld 同盟 / 门派标题 1 个文件。证明(scripts/lpcc_eval.py):sjplii 的 check_legal_name 在 HEAD 接受「张a」、改后拒绝,两边都接受「张三」;xkx100 的 clubpoem->mixup("离离原上草") 得到「离离原草上」(真交换汉字);nitan_san 两字同盟名得到 【 兄 弟 】。关键对象加载通过。

血条 / 表情标题按字节算显示宽度(2026-10-23,KB 06 §7.365)

老档把汉字当两字节:bar_string[0..(val * N / max) * 2 - 1] 把「格数」换成字节下标,半格血条在本驱动(按字符算区间)上会整条画满;巫师表情 edemote 的 repeat_string("─", 13 - (strlen(emotion) / 2)) 也会少减汉字宽度。scripts/lpc_fix_display_pad.py:去掉 CJK 血条 / 空白条上的 * 2(jings/qis 赋值和 blank_string3[0..(23-a)] 的 23→11 一并改),表情标题去掉 / 2;ASCII 空格的 none_string / empty_string 仍保留 * 2(一格两个空格)。本库 2 处(1 个文件)。证明(lpcc_eval.py hhsj):tribar_graph(6,9,12) 在 HEAD 画出整条 12 个■,改后为 6 个■+3 个□;麻将 strlen(tempB)/2 是两字符牌码,未改。

中文名 / 验证码仍按 GBK 取「两字节一字」(2026-10-23,KB 06 §7.373)

pname = pname[(random(sizeof(pname)) & 0xFFFE)..<1]; pname = pname[0..1]; 在按字符计下标时会取出两个汉字。scripts/lpc_fix_pname_slice.py 去掉掩码并把收尾改成 [0..0](pname/pname2/sec_name/str/str1)。本库 4 处。证明:nt1 生成名长度 2–4;HEAD 常见 3–5。

深度功能测试(2026-10-27,§6.10 未知 pragma save_binary/optimize)

删除本库中 FluffOS 不认的 #pragma save_binary / #pragma optimize… 行。