Ultimate Hell: Fingertip MUD Edition

✅ 可玩

终极地狱-指间MUD版

zjdyzj

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

▶ 开始游玩 · Play Now

游戏登录后自报家门为"地狱无门"("No Escape From Hell",压缩包标题为"终极地狱-指间mud版服务端"),是本项目"地狱"/Doing 血统六件套之一(与 `hell`、`zjdy2008wzb`、`zjdyaryl`、`zjdywzb`、`zjmudhell` 同源),逐字节地图比对显示与最近的姊妹版 `zjdyaryl`(爱若幽兰)高达 94% 一致,本作在同一套引擎核心之上额外接入了"指间MUD"手机客户端专属协议(crypt 挑战应答握手,账号/角色信息用特殊分隔符"║"整行拼接发送);新角色要经过一套自行设计的"投胎"仪式——先在阎罗殿从"猛士/智慧/耐力/敏捷/均衡"五种天赋偏属中选一种,再跳入"忘忧池"随机洗点,最后从十余个地名或"欧阳世家"等四大世家里挑一个出身,才能真正获得属性、结束"还没有出生呐"的状态;与名字同样带"终极地狱"字样但内容完全不同的 `zhongjidiyu` 并非同一款游戏。

English

Introduces itself in-game as 'No Escape From Hell' (the archive's own title is 'Ultimate Hell — Fingertip MUD Edition'). Part of the same six-way 'Hell'/Doing-lineage map family as this collection's hell, zjdy2008wzb, zjdyaryl, zjdywzb, and zjmudhell — 94% byte-identical (line-ending-normalized) to its closest sister zjdyaryl, sharing the same 'Hell' engine core with an added layer supporting the 'Fingertip MUD' mobile-client protocol (a crypt() challenge/response handshake, then an account/character-creation line joined with a literal '║' separator specific to this protocol). New characters go through a self-designed 'reincarnation' ritual: choose one of five temperament archetypes (fierce/wise/enduring/agile/balanced) in the Hall of Yama, jump into the 'Pool of Forgetfulness' to randomize stats, then pick a birthplace from a dozen-plus place names — or one of four great clans, e.g. 'House Ouyang' — before score will show anything but 'not yet born.' Not the same game as zhongjidiyu, another archive in this collection that also happens to carry 'Ultimate Hell' in its name. This mobile-protocol build never actually verifies an existing account's password on login (it simply overwrites the saved password with whatever the client sends), since the original design delegated authentication to the Fingertip MUD platform itself — a real deployment would need this patched.

README

内容亮点

深度功能测试新发现的 bug(§10.7,2026-08-04)

⚠️ 安全提示:这一"指间MUD"版服务端本身不校验老账号的密码(登录
时会直接用客户端发来的密码覆盖存档里的密码)。原设计把账号认证交给了
"指间MUD"平台,如需公开架设必须自行补上账号密码校验逻辑。

在线试玩

https://mudlibs.fluffos.info/zjdyzj/

管理员账号 / Admin account

警告:这是一个公开的默认密码,仅供本地/浏览器试玩。正式对外开服前
请务必修改此密码(并按上文安全提示补上密码校验逻辑)。

本地运行

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

游戏端口:40074。

NOTES · 移植与修复记录

zjdyzj — 地狱无门 ("No Escape From Hell") — archive #80

Archive: 终极地狱-指间mud版服务端.rar ("Ultimate Hell — 'Fingertip MUD' edition server"). Port: 40074. Status: done — boots clean, full registration with a real Chinese name works end-to-end in one continuous mudclient-style session, reaches the game world, and two post-login commands (look, score) both produce real, correct output.

Real in-game name (per config.hell's name directive, decoded): 地狱无门 ("No Escape From Hell" / lit. "Hell Has No Gate"). The mudlib root inside the archive is hell/. Lineage: ES II (per master.lpc's own header comment: *"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)"*) — same broad family as kxkj, kxkj1, yxcs, wuhanzhan, huoying, shenzhou, etc., but this specific hell/XKX branch is architecturally sound (real per-object F_DBASE set/query/delete via feature/dbase.lpc, not the nitan-family bare-simul_efun-dbase bug from AGENTS.md §15).

CROSS-CHECK FLAG for the main session: two other archives in this batch also carry the "终极地狱" name and were being processed by sibling agents concurrently with this one — archive #78 (终极地狱.rar → libs/zhongjidiyu) and archive #79 (终极地狱之爱若幽兰1.166正式版.rar → libs/zjdyaryl). I could not cross-check against them (concurrent extraction). Please diff/compare all three once all three are done — same game at different snapshots, unrelated forks sharing a name, or genuine duplicates. This one (zjdyzj) is confirmed ES II/XKX lineage (master.lpc header comment, secure-free adm/single/+adm/simul_efun/ layout, F_DBASE architecture) — cheap first thing to check against the other two once their own NOTES.md exist.

What makes the "指间mud版" ("Fingertip MUD edition") build distinctive

This is the answer to "investigate what makes this build distinctive" — and it turned out to be the single most impactful finding in this pass (see the crypt() section below), not just a cosmetic branding difference.

1. Two mostly-dead client-protocol headers are #included from globals.h (so present in every object): include/zjmud.h ("指间MUD 转义协议" / "Fingertip MUD escape protocol" — structured message-type tag macros like ZJTITLE/ZJEXIT/ZJPOPMENU/ZJCHANNEL meant for a rich mobile/web client to parse and render specially) and include/tomud.h (an older Tomud-client escape-sequence macro set, TMI/ALERT/PTEXT/L_ICON etc). Confirmed by grep that neither header's actual protocol macros are ever called anywhere in the lib (grep -rn "ZJTITLE\|ZJEXIT\|ZJOBIN\|TMI(\|ALERT(" → zero hits outside the headers themselves) — these are vestigial/unwired, presumably copied in from a sibling build that did use them, or prepared for a client integration that was never finished. 2. What IS actively wired in: zjmud.h's ZJKEY constant and two simple numeric tag macros (SYSY = "000", ZJTMPSAY = "000" + "015" equivalent via ESA="" prefix) are used throughout logind.lpc — a plain telnet client just sees literal digit sequences (0008, 015) glued to the front of certain system/registration messages (visible in the transcript below, e.g. 0000008/0000007) — cosmetically odd over plain telnet but not a functional blocker. 3. The real distinctive feature, and a genuine driver-compat bug: logind.lpc's login flow requires a crypt()-based client-version handshake before any account/character data is even accepted — logon() sends "ver1.0," + crypt(ZJKEY, 0), then jiance() expects the client to echo back crypt(ZJKEY, <received-string>[2..3]). On the ORIGINAL driver this targeted, crypt(key, 0) produced a deterministic 13-char old-style DES-crypt string (2-char salt + 11-char hash), so a real client could compute the matching response. On this FluffOS build, passing an int (not a string) as crypt()'s salt argument falls through to a completely different code path that generates a FRESH RANDOM SHA-512 ($6$...) salt every single call (confirmed by reading src/packages/core/efuns_main.cc's f_crypt()), and the 2-character slice str[2..3] the mudlib then uses as a "salt" for the verification call doesn't parse as any recognized modern salt prefix either (also random). Empirically verified this makes the handshake mathematically unpassable by ANY client — two consecutive crypt(ZJKEY, str[2..3]) calls given the identical str never produced the same output. This silently blocked 100% of connections at the very first prompt, with zero compile error and zero debug.log entry — it just looks like "wrong client version" to anyone testing it. Fix: changed crypt(ZJKEY, 0) → crypt(ZJKEY, "zj") (an explicit old-style 2-char alphanumeric salt, not starting with $) in adm/daemons/logind.lpc's logon() — this driver's crypt() still honors that as the classic deterministic DES-crypt path, restoring the original request/response design so a real client (or our own test client, using Python's crypt module) can compute the correct reply. This is a new pattern, not previously in AGENTS.md's catalog — worth promoting to a §15ai-shaped entry: *"crypt(key, 0)/crypt(key, int) salt semantics changed on this driver — always generates a random modern hash instead of a deterministic old-style one; any lib doing a client-side challenge/response based on slicing a crypt(..., 0) result needs an explicit short (non-$-prefixed) string salt instead."*

Fixes applied (with why)

Pre-boot proactive checks (per the task's required list) and what was found:

1. §15h (is_chinese/named.lpc GBK byte-range bug) — YES, present, fixed. - adm/simul_efun/chinese.lpc's is_chinese(str): GBK lead/trail-byte range check (str[i]<176||str[i]>=248 etc, stepping by 2, requiring even byte-length) → rewritten as a straight CJK Unicode codepoint range check (str[i] >= 0x4e00 && str[i] <= 0x9fff), one character per index, strlen(str) < 1 minimum instead of < 2. - adm/daemons/named.lpc (the "similar name" / name-collision guard used during registration) had the full documented shape: #define PATH(name) (name[0..1] + "/" + name) (sharding key = "first GBK character", now taking 2 real characters) → name[0..0]; every strlen(name) < 2 guard (meant as "at least 1 character") → strlen(name) < 1; the sliding-window near-miss check's byte-widths 4/6 (2/3 GBK characters) → character-widths 2/3 (name[i..i+1] / name[i..i+2], loop bounds l-2/i+3<=l). - Verified end-to-end: sent real Chinese names 秦风 and 夏流 through registration in two separate full sessions and both were accepted straight through to character creation and into the game world.

2. §15ae (private add_action hook) — YES, present in TWO places, both fixed. - feature/command.lpc's command_hook(string arg) — the central add_action-registered command dispatcher, inherited into the player body class — was declared private nomask. Dropped private, kept nomask. Without this fix every single typed command (look, score, everything) would have silently done nothing after a successful login, with zero error anywhere — exactly the failure mode this policy exists to catch. - New instance found by grepping every add_action() target lib-wide for a private declaration in the same file (not just the obvious central dispatcher): adm/npc/luban.lpc (a crafting/house-building NPC, "鲁班" — the legendary Chinese master carpenter) registers 18 of its own commands (do_answer, do_stop, do_desc, do_show, do_changename, do_changeid, do_changetype, do_changedesc, do_finish, do_withdraw, decide_withdraw, do_demolish, do_help, do_list, do_type, do_agree, do_reject, do_delete) via add_action, ALL declared private (consistent with the rest of that file's style, which declares essentially every helper function private). Removed private from just those 18 forward-declarations + definitions (left every other, non-add_action private helper in the file untouched). Without this, talking to 鲁班 and typing any of his commands would silently do nothing.

3. New crypt()-semantics bug (see distinctive-build section above): adm/daemons/logind.lpc's logon()/jiance() client-version handshake — fixed by using an explicit 2-char salt instead of 0. The single highest-impact fix in this pass — nothing past the very first prompt was reachable before this.

4. §15s (message() 4th-arg type bug) — YES, present, fixed, found live during boot testing. adm/simul_efun/message.lpc's message() wrapper forwarded its exclude parameter straight to efun::message() with no guard; the extremely common 2-arg tell_room(str, target) / bare shout()/say() call shapes leave exclude as a raw unset int 0, which this driver's runtime efun-argument type checker rejects (void|object|object* expected, got int). Confirmed live: this crashed questd.lpc's collect_all_quest_information() → channeld.lpc's do_channel() broadcast and cpud.lpc's create() during ordinary preload, before any player ever connected. Fixed once at the shared root: efun::message(arg, message, target, (objectp (exclude) || pointerp(exclude)) ? exclude : ({})).

5. §2 second variant (fixed-width extension-strip slice, .c→.lpc rename fallout) — YES, present in 5 call sites across 3 files, all fixed. [0..<3] (correct for stripping a 2-char .c extension) was never widened for the 4-char .lpc extension after the rename; widened to [0..<5] in: - adm/daemons/eventd.lpc:23 (event_name = map_array(event_name, (: $1[0..<3] :)) after get_dir(EVENT_DIR + "*.lpc")) — this one was caught live in the very first boot attempt (call_other() couldn't find object '/adm/daemons/event/emei.l', the truncated filename visible directly in the error). - adm/daemons/storyd.lpc:57 (identical shape, story_name). - adm/single/importer.lpc:563 (item_name[0..<3], comment on the line literally already says *"要去掉后面的 .c 字样"* — "need to strip the trailing .c"). - adm/npc/luban.lpc:1522 and :2753 (DBASE_D->clear_object(...) dbase-key derivation from a .lpc filename, both same fixed-width slice bug). - Confirmed NOT the same bug for every other [0..<2] occurrence found lib-wide (11 hits: configd.lpc, ftpd.lpc×2, versiond.lpc ×2, channeld.lpc, npcd.lpc, scheme.lpc×2, call.lpc, config.lpc×3, combine.lpc) — spot-checked each; these strip a trailing single character (CR/space/last array element) or the 2-char .o save-extension (__SAVE_EXTENSION__, unaffected by the rename), unrelated to .lpc. Also confirmed importer.lpc:191's [0..<3] is a THIRD, unrelated, correct use (stripping .o from a player-save directory listing under login/, not a .lpc listing).

6. New finding (not previously cataloged): a get_config() numeric-index mismatch between this mudlib's own include/runtime_config.h and this driver's real config-slot numbering. The mudlib's copy defines __MUD_PORT__ as CFG_INT(0) with BASE_CONFIG_INT = 14 (→ absolute slot 14), matching some older/different FluffOS build's layout. This driver's actual src/include/runtime_config.h uses BASE_CONFIG_INT = 256 (after RC_LAST_CONFIG_STR = CFG_STR(255)), so __MUD_PORT__ is really absolute slot 256 — meaning every get_config(__MUD_PORT__) / LOCAL_PORT() call in this mudlib silently reads the WRONG config slot (whatever driver-internal value actually lives at slot 14, a CFG_STR slot here, not a CFG_INT one). Confirmed the concrete symptom: adm/daemons/network/messaged.lpc's startup_udp() calls socket_bind(socket_id, my_port) where my_port = LOCAL_PORT() + MESSAGE_PORT ends up evaluating to the STRING "10" instead of an int port number, throwing Bad argument 2 to socket_bind() Expected: int Got: "10" and failing the whole object's create() (surfaced via the lpcc_check.sh sweep, not the real boot, since — separately — messaged was never actually reachable in real play anyway, see next item). Not fixed (out of scope/low practical impact — see below); flagging as a new AGENTS.md-catalog-worthy pattern (§15aj-shaped: *"a mudlib's own runtime_config.h copy can have stale get_config() slot numbers from a different historical driver build; only trust the numbers if a value spot-check via a real interactive test confirms they line up, don't assume matching macro NAMES means matching NUMBERS across driver versions"*). - Practical impact assessed as low, so not fixed: the only two consumers found (versiond.lpc's inter-station-sync port and messaged.lpc's UDP messaging port) are both peripheral, non-preloaded, optional network features — neither blocks registration or ordinary solo gameplay, and both already fail gracefully in their own way (versiond.lpc sets its readiness flag *before* the broken call per §15ad below; messaged.lpc is simply never preloaded at all — see next item). Fixing the mudlib's whole runtime_config.h copy to match this driver's real numbering would be the "correct" fix but is broader than this pass's scope; noted here for whoever picks up cross-lib get_config() audits next.

7. Separately found (also not fixed, same conclusion): adm/etc/preload lists /adm/daemons/messaged but the real file is /adm/daemons/network/messaged.lpc — a path typo/drift, so this daemon has never actually been preloaded in this archive at all (master.lpc's preload() silently no-ops on a file_size(...) == -1 miss). Left as-is deliberately: fixing the path would newly expose the get_config() bug above (item 6) at every boot, trading one dormant bug for a newly-active one, for a peripheral inter-mud-messaging feature that isn't part of registration or basic play. Documented here rather than silently "fixed" in a way that would make things worse.

8. New bug (not previously cataloged), found live in clone/user/user.lpc — the exact is_killing shape from AGENTS.md §15b's nitan_ceshi precedent, independently recurring in this unrelated lib: accept_kill(object ob) { if (is_killing(ob)) ... } — is_killing is declared varargs int is_killing(string id) in feature/attack.lpc and every one of its other ~25 call sites correctly passes ob->query("id"); this one direct (non-->) call passed the object itself, a static type mismatch this driver's compiler rejects outright. Since user.lpc is the player body class, this took make_body() down with it — the account+id got accepted, but character creation (get_char) then silently failed to ever produce a working player body (confirmed: this was the actual blocker once the crypt-handshake fix let a real session reach this far). Fixed: is_killing(ob->query ("id")). A second, independent instance of the identical shape found via the lpcc_check.sh sweep in d/city/npc/guidao.lpc (an NPC, "鬼刀王五") — same fix applied.

9. New bug (not previously cataloged), found via the lpcc_check.sh sweep, one shared root cause per §8g: inherit/misc/quest.lpc's set_information(string key, string info) wrapper (and the matching forward declaration in include/quest.h) declared its second parameter as a bare string, but the real underlying implementation it forwards to (questd.lpc's set_information(object, string, mixed info)) — and every one of 7 clone/quest/*.lpc call sites (supply, explore, shen, search, capture, deliver, judge) — actually pass a function closure ((: ask_npc :), an F_DBASE-style deferred evaluated info display), not a string. Widened both the wrapper and the header declaration to mixed info, resolving all 7 sweep failures in one edit.

10. Three more one-off pre-existing typos found via the sweep (not encoding-conversion fallout, confirmed genuine authoring mistakes): - inherit/condition/damo.lpc's die_reason(): missing closing quote + missing + before a trailing string literal (return name + HIM "发作身亡了; → return name + HIM + "发作身亡了";) — the classic §10-shaped "swallowed the rest of the file into one malformed string" bug, surfacing as a syntax error many lines later. This one shared header is inherited by 3 more files (damo_luanqi, damo_shangshen, damo_zheyuan), all fixed by the single edit. - d/village/npc/obj/shoes.lpc: a set("icon", "05047"); statement was spliced into the middle of an unrelated set("long", ...) call's argument list, breaking it into a syntax error. Restored as two separate statements in the evident original order. - d/baituo/obj/bowl.lpc (and its exact duplicate, d/baituo/npc/obj/bowl.lpc): set_name("海口大碗", "bowl") passed a bare string where set_name(string name, string *id) requires an *array* of id strings (confirmed: 2850/2852 other set_name() call sites in the whole lib use the array form correctly) — fixed to set_name("海口大碗", ({ "bowl" })). - feature/attribute.lpc's query_per(string arg): required a string arg that is never read inside the function body at all, and all 6 call sites lib-wide omit it — 5 via -> (call_other, which tolerates the arity mismatch) and one direct call in kungfu/class/generate/girl.lpc (per = query_per();), which hit this driver's strict same-object argument-count check and failed to compile. Added varargs to match the calling convention actually used everywhere (same "direct call enforces the signature strictly" shape as §15b, just missing-arg instead of wrong-type).

11. §15e/§13 (unguarded factory-call chain onto possibly-missing content) — found and fixed, broadly protective. inherit/room/room.lpc's make_inventory(string file) did ob = new(file); ob->set(...) with no check that new() actually returned an object; new() legitimately returns 0 when file points at content genuinely absent from this archive (§13: real archive gap, not something to fabricate). Root-caused via d/beijing/gaosheng.lpc referencing d/beijing/npc/chaboshi.lpc, which does not exist anywhere in the archive (confirmed: 3 of its 4 listed NPCs do exist, one doesn't — a genuine partial-content gap, not a systemic one). Added an objectp(ob) guard inside make_inventory() itself, AND (this part matters) at its one call site in reset()'s case 1: single-object branch, which called straight into ->is_character() on the (possibly-0) result with no guard at all — the sibling default: (multi-object) branch already had this guard via objectp(), so this brings the single-object path up to the same standard rather than introducing a new pattern. This is a general robustness fix (protects any OTHER room in the lib with a similar missing-NPC gap), not just for gaosheng.

Config file notes

Checked, confirmed NOT applicable (per the task's proactive checklist)

Registration-flow test (CRITICAL instruction) — full transcript summary

Ran two complete, independent, single-continuous-connection sessions via a custom scripted client (mudclient.py's static --send list can't handle this lib's dynamic crypt-challenge — wrote a small one-off Python client that computes the correct crypt() response from the server's own banner using the standard-library crypt module, which matches this driver's classic-DES-crypt path for a non-$-prefixed 2-char salt):

1. Connect → receive ver1.0,<random-$6$-hash> → compute and send crypt(ZJKEY, str[2..3]) → "版本验证成功" (version verified). 2. Send id║password║crypt(ZJKEY,id)+crypt(ZJKEY,password)║email (new account, e.g. xialiu99║test12345║...║[email protected]) → accepted, advances to character creation (0008 code). 3. Send gender║avatar-id║name with a real Chinese name (男性║1║夏流, then separately 男性║1║秦风 on the first run) → accepted, make_body() succeeds, "你连线进入了地狱无门" ("you have connected into 地狱无门") — confirmed landed in the actual game world (starting room "世外桃源"/Peach Blossom Spring, full room description, exit list, two NPCs present, all rendered correctly in UTF-8). 4. Sent look → full real room re-render (description, exits with flavor text, NPC/object list) — confirmed working. 5. Sent score → "还没有出生呐,察看什么?" ("You haven't even been born yet, what are you looking at?") — this is correct, in-character behavior, not an error: the welcome banner explicitly tells new players they must first walk through the cardinal-direction "投胎" (reincarnation) minigame at the starting room before their character is fully "born" with real stats; score correctly refuses until that completes. Confirmed intentional by reading the actual welcome text and room exits (west:阴险奸诈 / east:光明磊落 / north:心狠手辣 / south:狡黠多变 — four different "innate quality" paths). 6. Sent quit → clean disconnect, "欢迎下次再来!" ("welcome back next time"). 7. Confirmed via data/login/x/xialiu99.o and data/user/x/xialiu99.o (and the q/qinfeng1.o pair from the first run) that real save files were written to the correctly-sharded directories. 8. debug.log across both full sessions shows zero new runtime errors beyond the two already-understood, already-cataloged benign Bad argument 2 to socket_bind() lines from versiond.lpc (§15ad pattern — version_ok is already set synchronously before the failing async socket call, confirmed by reading the code, not just observing the harmless symptom).

This satisfies the standing policy (§15ae) of testing at least one real post-login command after registration — done with two (look, score), both producing correct, real, in-character output.

lpcc_check.sh sweep results

7302 files total. 7289 PASS, 13 FAIL (all 13 triaged and confirmed non-bugs, not fixed — see lpcc_fail.log for full detail):

All other 7289 files compile clean.

Summary of what was confirmed working vs. left as known issues

Working, verified: encoding (zero stragglers after the standard conversion + straggler re-scan), .c→.lpc rename + all ".c" reference fixes (zero uppercase .C files either), boot (zero fatal errors), Chinese-name registration end-to-end, character creation, entering the game world, look, score, quit, save-file persistence, the command_hook/add_action dispatch chain for ordinary play.

Known, documented, deliberately not fixed (peripheral, non-blocking): the get_config() runtime-config-index mismatch (affects only versiond's inter-station-sync port and messaged's UDP port, both optional network features); messaged's wrong preload path (leaving it undiscovered is currently the safer state given the above); the two unwired "指间MUD"/Tomud client-protocol headers (harmless dead code, documented for context); DNS/intermud calls reachable only from who/mudlist/shutdown/telnet/reboot admin commands (never exercised by the required test, not touched).

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:

- get_id() (~line 262): VERSION_D->is_version_ok() gate guarded with find_object(VERSION_D) (absent ⇒ version ok — the WASM sockets-less case). Note this legacy get_id path isn't part of the zjmud get_user flow, but the guard is kept for completeness. - check_ok() (~line 414): MESSAGE_D->find_chatter() wrapped in catch() — same first-login-killer shape as the sibling hell libs (messaged's create() can throw; under WASM it fails to compile entirely and the call itself throws).

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

200s scripts/wasm_boot_watch.sh sit surfaced one genuinely new, previously-undocumented finding beyond the already-known ftpd.lpc/dns_master.lpc/versiond.lpc sockets-absent preload class (§1.3c, caught/cosmetic): inherit/item/combined.lpc's private void destruct_me(), self-invoked via call_out("destruct_me", 0) when a money stack's amount hits 0 — denied with "apply() with insufficient permission ... function: destruct_me, origin: internal, needs: private, has: hidden" on clone/money/silver#581. Same root cause as this lib's own already-fixed command_hook class (AGENTS.md §8.3a: private demotes to DECL_HIDDEN once inherited; a self-invocation via call_out/command() needs DECL_PRIVATE and gets silently denied) — spent-down money stacks were never actually self-destructing (harmless but permanent per-object leak). Fixed: dropped private, kept the function otherwise unchanged. Same fix ported to siblings zhonghua2 (where the identical bug also fired live), zhongjidiyu, zjdyaryl (latent there, didn't fire in their sits). Retest via a small one-off Python client (crypt-module challenge/ response, same shape as the original verification pass) confirmed registration → look → score → quit still clean; a fresh 200s WASM re-sit confirmed the "insufficient permission" line no longer appears.

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

同一核心加上指间mud手机协议。这是这个家族里唯一一个反映真实、仍然生效的注意事项而不是过时状态的 'limited'(和已经在本轮修正为 playable 的手足档案 zhongjidiyu(052)/zjdyaryl(053)不同):这个移植版本的 logind.lpc 把每一个连线都挡在一个为'指间'(手机)客户端设计的自定义基于 crypt 的客户端版本握手之后,而不是普通的 telnet/网页终端会话。服务器发送 'ver1.0,<crypt(ZJKEY,"zj")>',期待客户端回复 crypt(ZJKEY, <那段字符串>[2..3])——本轮已确认这个握手过程确实是完全确定性的、可以通过的(这个驱动的 crypt() 和标准 libc 的 DES-crypt 完全一致,已用 Python 的 crypt 模块重现服务器自己的挑战值并计算出一个服务器能接受的回复,'版本验证成功'来验证),之后还有第二个自定义协议步骤:一整行输入,组合了 id║密码║crypt(ZJKEY,id)+crypt(ZJKEY,密码)║电子邮件(║ 是 U+2551)。手动完成这两个步骤能干净地到达完整游戏流程(管理员登录已确认,'目前权限:(admin)',quit 正常)——但这个项目提供的通用 WASM 网页终端(一个普通的类 telnet 界面)无法自动计算出这些值,所以普通访客点进这份档案的页面,没有外部工具是过不了第一个提示的。另外还修复了和手足档案相同的一个真实 bug:clone/user/login.lpc 的 query_save_file() 空字符串保护(既检查 id == "",也检查 !stringp(id)),阻止第一次连线时的 %c/sprintf 崩溃。

深度功能测试(§10.7,2026-08-04)

此前的会话只做到"注册流程"(look/score 两条真实指令),本轮在此基础上 真正走完了完整流程:创角后的"投胎"仪式、地图移动、商店、战斗、死亡、 以及无人打扰的完整复活序列。原生 driver(端口 40074)。

测试工具:由于这份档案自创的手机客户端协议(见上一节), mudclient.py 无法直接用,本轮写了一个一次性 Python 客户端(用标准库 crypt 模块复现驱动自己的 crypt(ZJKEY, salt) DES-crypt 挑战/应答), 能完整驱动"版本验证→账号注册/登录→创角→游戏内指令"的连续会话;后续 针对死亡复活的等待验证则单开一个只做登录、然后静默等待的连接,避免任 何指令打断 call_out 链。

主动检查(对照 AGENTS.md 已归档的 bug 类):

新发现并修复:第 11 例 §7.68 复活软锁死(d/death/npc/ {wgargoyle,bgargoyle}.lpc,白无常/黑无常)——DEATH_ROOM (/d/death/gate)摆着 wgargoyle,其 north 出口 /d/death/gateway 摆着 bgargoyle,两者都可达。两个文件的 death_stage() 都是 if (!ob || !present(ob)) return;——玩家在复活序列进行中如果暂时不在 场(例如短暂 net-dead),会被永久放弃,鬼魂永远卡在鬼门关。已按标准 修法拆分为"彻底缺失(!ob)→放弃"与"暂时不在场(!present)→ 5 秒后重 试"两种情况,两个文件都改了。

完整游玩记录: 1. 用真实中文名"沈牧"(id shenmu)完成账号注册(含邮箱)和创角 (男性/沈牧),落地"世外桃源",此时 score 正确提示"还没有出生 呐"(游戏内合理行为,不是 bug——档案还没走完投胎仪式)。 2. 探索四个方向房间确认"阴险奸诈/光明磊落/心狠手辣/狡黠多变"四种天性 任你选(这是这份档案自己设计的性格/命运系统,d/register/ yanluodian.lpc 里"偏属→洗点→转生"的三步流程),进入"阴险狡诈"房 间后 out 到"阎罗殿",依次 pianshu msx(选择猛士型)、wash (在忘忧池随机洗出四项天赋:膂力24/悟性17/根骨19/身法20)、 born 中原人氏,成功"投胎"落地"悦来客栈",获得布衣/牛皮靴, score 此时正确显示完整数值面板。 3. 移动测试:悦来客栈→南阳城→迎宾楼(list 正确显示三样商品和价 格)→尝试 buy baozi from xiao er,因为没有铜钱被店小二正确拒绝 ("穷光蛋,一边呆着去!"——内容行为,不是 bug)→大驿道→汝州城, 沿途场景描述、出口列表、NPC 列表均正常渲染。 4. 战斗测试:wimpy 0 后 kill xiao fan(南阳城的小贩),完整回合制 拳脚对轰后成功将其打死,期间技能"基本拳脚/基本招架/基本轻功"多次 熟练度提升提示正常触发;随后在汝州城 kill wu jiang(武将,带钢 剑),两回合内被连续刺伤/砍伤直接打死(wimpy 0 生效,没有自动 逃跑),角色武力值太低是内容/数值现象,不是 bug。 5. 死亡→复活:死亡后正确落地"鬼门关"(DEATH_ROOM),白无常在场。 在完全不打断的情况下(关闭连线后等待、再用另一条静默连接确认 最终状态)验证:death_stage() 五段对话全部走完,reincarnate() 成功,角色最终落地 /d/city/guangchang("泥潭广场"),score 显示"你总共到黑白无常那里串门一次",quit 正常退出。这正是修复 后应有的行为——如果没有这次的拆分修复,任何一次连线中断都会让角 色永久卡在鬼门关。 6. mail 指令确认"此服务已经暂停"——功能性下线,属内容/运营决定,不 是 bug;未发现玩家可用的公共留言板入口(cmds/arch/board.lpc 是巫 师级指令,inherit/misc/bboard 没有被任何 d/ 房间挂载),本轮未 深入排查这是设计如此还是遗漏,留作后续观察,不视为确认的 bug。

结果:整个测试会话(含两次死亡复活循环)debug.log 全程为空,没 有任何 error:/Bad argument/No program/Too deep recursion 记 录。测试角色存档(data/{login,user}/s/shenmu.o)保持未跟踪;两个死 亡 NPC 文件的改动已用 formatter 校验({"errors":0})。

更正(2026-08-05):§7.68 复活软锁"修复"已撤销

上面提到的"鬼魂离开/不在场时被永久放弃复活流程"曾被当作 AGENTS.md §7.68 记录的一类 bug 修复(把单次判定改成每 5 秒重试)。经用户指出并 重新审视:这更可能是有意的游戏设计,不是 bug——大多数这类档案里 鬼魂根本无法自行移动,所以"不在场"要么从未真正发生,要么是"离开去 在阴间游荡,想回来时再走回这个房间、流程会通过 init() 重新从头开始" 这种有意为之的宽松机制,而不是需要强制追上玩家的错误。强行重试还可能 引入新问题:如果鬼魂之后又走回这个房间,旧的重试和 init() 重新触发的 新一轮流程可能同时运行,导致对话重叠错乱。已把这处改动撤销,恢复成 原始的 if (!ob || !present(ob)) return; 单次判定写法(bmxkx2001 除外——那份档案里这确实是一个真实存在、经过实际复现验证的 bug:鬼魂 本身完全无法移动,是另一个不相关的 NPC 强行把鬼魂拖走导致的)。详见 AGENTS.md §7.68 顶部的撤销说明。

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

AGENTS.md §7.100 fix (2026-08-19): redundant replace_program(ROOM) landmine

Same corpus-wide bug as the batch-1-6 sweep (ROOM macro "/inherit/room/room" from include/globals.h). Deleted 2,315 live standalone replace_program(ROOM); lines under work/ via fix_710_room.py, plus hand-fixed the room-building tool's string-builder template (work/clone/misc/roommaker.lpc). No .lpc files under work/data/, so no false-negative risk. Remaining matches after the fix are all pre-existing //-commented.

Verified: clean build-debug boot (zero new compile errors, zero "cannot replace"/"cannot bind" in debug.log), live admin login through this lib's custom 指间MUD crypt()-challenge login protocol (reused the DES-crypt handshake this lib's own NOTES documents above: reply to ver1.0,<str> with crypt(ZJKEY, str[2:4]), then send id║password║crypt(ZJKEY,id[:2])+crypt(ZJKEY,password[:2])║email) as fluffos/Mud@2026 — banner confirmed "目前权限:(admin)", look/quit both worked cleanly. Incidental data/{login,user}/f/fluffos.o save drift from the login test was reverted via git checkout HEAD 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): 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.

§10.7 gap closure pass (2026-08-24): shop purchase, apprenticeship, board

Closed the three items flagged as genuinely open by the 2026-08-04 §10.7 pass (buy never completing, apprenticeship never tested, board existence unresolved). Native driver, port 40074, via a one-off Python client reusing this lib's own crypt/ZJKEY handshake (same recipe documented above). Tested through the seeded admin account (fluffos/Mud@2026, wizard-tier goto used to reach each location instantly instead of walking) rather than a fresh registration, since none of the three mechanics below gate on born/registration state — confirmed by reading each code path first. All three came back clean; no bugs found, nothing fixed.

1. Successful shop purchase, with real money math: goto /d/city/zuixianlou ("醉仙楼", one of the two starting-town shops — this is the specific room the earlier "迎宾楼" note was referring to, NPC 店小二/xiao er, inherit F_DEALER in feature/dealer.lpc). list correctly showed 4 goods incl. 包子(baozi) at 50 文. buy baozi from xiao er before funding reproduced the known insufficient-funds rejection. Funded via wizard clone /clone/money/coin 200 (200 文铜钱), then buy baozi from xiao er succeeded: "你从店小二那里买下了一个包子。" Inventory after: baozi present, money correctly re-denominated from 200 文铜钱 down to 一两白银 + 五十文铜钱 (150 文 = 200 - 50, split back into silver+coin by adm/daemons/moneyd.lpc's player_pay()) — change math is correct. (Buying 2 at once correctly failed with "包子只能 一个一个的买。" — baozi.lpc isn't a combinable/stackable object, content behavior not a bug.) 2. Sect/apprenticeship system: exists, distinct from the 偏属/命运 personality system, and works. Real mechanic via apprentice/bai command (cmds/skill/apprentice.lpc + bai.alias) and feature/apprentice.lpc (is_apprentice_of/recruit_apprentice/ create_family, family mapping with family_name/generation/ title). Sect NPCs seed their own lineage via create_family(...) in create() (e.g. d/quanzhen/npc/zhangyuan.lpc "掌园道长" — 全真教, generation 4) and each NPC decides in its own attempt_apprentice() whether to accept (many refuse by design — d/baituo/npc/trainer.lpc demands born_family == "欧阳世家", d/xingxiu/npc/caihua.lpc/d/yanziwu/npc/bao.lpc refuse unconditionally — content, not a bug). goto /d/quanzhen/xiaohuayuan1 ("小花园") then apprentice zhangyuan went through cleanly end to end: "掌园道长说道:好吧,我就收下你这 个徒弟了。" → "恭喜您成为全真教的第五代弟子。" me->query("family") correctly populated. No sect-master NPC search was needed beyond this one confirmed instance — the mechanism is generic and this is enough to confirm it works. 3. Player-usable public message board: exists, was mis-diagnosed as absent in the 2026-08-04 pass. That pass only checked cmds/arch/board.lpc (wizard-only, correctly ruled out) and whether bare inherit/misc/bboard was mounted anywhere (correctly found not mounted) — but missed the actual mechanism: inherit/misc/bboard.lpc is the base class for **clone/board/*.lpc (47 distinct board objects), each a BULLETIN_BOARD-inheriting clone with its own board_id/location, foo()'d directly into a room's create() (e.g. d/city/chaguan.lpc's "/clone/board/player_b"->foo();). player_b.lpc ("江湖恩怨留言板", capacity 100) sits in /d/city/ chaguan ("春来茶馆"), one hop from the game's starting area, and is visible in the room's object list to any player who looks. Its post/read/discard commands are plain add_actions available to everyone (only gated by mud_age >= 1800/jing >= 50 for non-wizards, both waived for wiz_level(me) >= 1, which is why the admin account could exercise it directly). Live-tested: goto /d/city/chaguan → post 深度测试留言 → wrote one line → . to finish → "留言完毕。" → read new → note rendered correctly with title/author/timestamp. Of the 47 mounted boards, most others are per-sect (quanzhen_b, shaolin_b, wudang_b, etc., also visible as room objects — e.g. /d/quanzhen/damen lists "全真派弟子留言 板" — presumably gated to that sect's members by the individual board's own logic, not inspected further since the general player-facing case was already confirmed). Conclusion: this lib does have a real player-facing board; the design is "many small boards scattered through the map, not one central board command."**

Debug.log stayed completely empty (no error:/Bad argument/No program) across the whole session. Incidental save drift (data/{login,user}/f/fluffos.o from the admin login/purchase/ apprentice test, data/dbased.o from ambient quest-daemon ticks, and the new data/board/player_b.o created by the test post) was reverted/ removed before committing — admin account and world-state files are back to their pre-test committed contents.

深度功能测试(§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 zjdyzj --fix 加 scripts/lpc_fix_no_effect.py --apply,共改动 538 个 .lpc 和若干 .h。HEAD 与工作树分别加载(新进程):536 -> 536 通过,无回退;改过的头文件的 128 个包含者 127 -> 127 通过。 手工(与 zjmudhell 相同):28 处 Illegal to declare nosave function——include/globals.h 的 #define nosave nosave 让扫描工具认不出这个关键字,手工改成 protected(这些函数只被本对象回调或驱动程序调用);adm/daemons/natured.lpc 的空语句 lt[LT_MON];、adm/daemons/test.lpc 的空语句 your_temp["guarding"]; 删去。都不改变行为。

启动时警告 55 -> 0。新进程登录画面正常;普通 telnet 输入名字后答"客户端非法"——指间 MUD 的客户端握手(KB 01 §1.7),HEAD 也一样。

深度功能测试(§10.7,2026-10-11):“无副作用表达式”警告复查

深度功能测试(§10.7,2026-10-12):编译警告收尾

能编译的文件里的警告 3 -> 0(toolkit_fix.sh zjdyzj 扫描:7289 个文件可编译,0 条诊断)。与 nitan170911/wxddym 当天修过的同一批文件,照它们的做法手工改;ZJ 手机客户端登录协议代码没有动。

真问题:

其余:

验证:改动文件、所有继承 F_FOOD/F_LIQUID 的物品、包含 pigpart.h 的文件、eat/drink 命令共 206 个对象,HEAD 与工作树各用新进程载入:205 -> 205 通过,regress 0;删除声明审计 0 项待看。206 个对象载入无回退。

负数伤害当作消耗(2026-10-15,KB 06 §7.241)

receive_damage("neili", -52)、receive_damage("qi", -12) 之类把 add() 的负数写进了 receive_damage():本库 feature/damage.lpc 对负数伤害报错(或归零),练功/运功时报 F_DAMAGE: 伤害值为负值 或消耗失效。已改为正数伤害,内力/精力类改为 add(type, -N)(scripts/lpc_fix_damage_calls.py):/kungfu/skill/duanyun-fu、/kungfu/skill/feifeng-whip。

family 映射(再补)(2026-10-15,KB 05 §7.30)

mapping fam; fam = (mapping)ob->query("family");(先声明后赋值)和泥潭式的 query("family", me) 同样直接取下标:无门派的新人拜师(attempt_apprentice/recognize_apprentice)、NPC 的 greeting、房间的 valid_leave、ask_me 等报 Value being indexed is zero。2 处补上 if (!mapp(fam)) fam = ([ ]);(刚 recruit_apprentice() 之后的除外)。

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 的身体,这一改动不改变行为。

技能名拼错(2026-10-15,KB 06 §7.249)

query_skill() 里的技能名拼错了,或用的是本库改名前的旧名;本库没有这个技能,调用永远得 0:门槛(< N 就拒绝)永远过不去,相关功夫或绝招学不了、用不了;内功互斥(> N 就拒绝)从不生效;公式里这一项一直是 0。经汉字名(提示文字或中文名词典)或纯拼写差异(_/-、少一个连字符、相邻字母颠倒)核对后,改为本库的技能名(scripts/lpc_fix_skill_typo.py):choushui-zhang→chousui-zhang(chousui-zhang);cuhixiao-jifa→chuixiao-jifa(xiaoao-jianghu);lenyue-shengong→lengyue-shengong(lengyue-shengong);matrial-cognize→martial-cognize(qiankun-danuoyi/nuoyi);taiji-shengon→taiji-shengong(taiji-shengong)。

技能名拼错:技能文件里拼错自己的名字(2026-10-15,KB 06 §7.249)

技能自己的文件(或它的绝招目录)里,query_skill() 读的是差一个字母的自己的名字,永远得 0:“基本功夫要高过本技能”之类的限制从不生效,按等级取招、计算伤害的那一项一直是 0。改为本技能的名字(scripts/lpc_fix_skill_typo.py 的 self 判据):shicheng-liulang→shicheng-liulong(shicheng-liulong);xuanyuan-arror→xuanyuan-arrow(xuanyuan-arrow)。

柔云步法「柔云纵」有时报错(2026-10-15,KB 06 §7.251)

rouyun-steps/zong 随机把人送到三个房间之一:精、精力已经扣了,“乘云而去了”也说了,送不过去时人留在战斗里并报错。第三个是巫师的测试房 /u/deaner/test1,库里没有;改为 /d/city/guangchang(hy、jym 等库原本就用这个)。

family 映射内联取下标(2026-10-15,KB 05 §7.30)

ob->query("family")["master_name"]、me->query("family")["family_name"](泥潭式为 query("family", ob)[...])直接对门派映射取下标:没有门派的人(新玩家、叛师者、大多数 NPC)在 NPC 的打招呼和问话、绝招的门派判断、房间的 valid_leave 里报 Value being indexed is zero,而不是得到拒绝。1 处(1 个文件)改为 dbase 路径查询 query("family/master_name"):有门派时值相同,没有时得 0;原有的判断照常生效(scripts/lpc_fix_family_index.py)。给映射赋值的写法未动。

没有对手的绝招全部报错:message_combatd()(2026-10-15)

指间版 message_combatd(msg, me, you, ...) 为客户端加了图标和伤害行,直接读 you->query_temp("armor/armor")、you->query("qi")。不对别人出手的招(混天气功、龙象功等的 roar,先天功 shield,金刚不坏体 jingang ……)只传 msg, me,you 是 0,每次使用都报错,绝招探测里本库有 28 个这样的招。照同系 zjmudhell、wxddym、wdxtym 的写法,伤害行包进 if (you);28 个招全部能用了。

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)

武功名拼错的 NPC(2026-10-19,KB 06 §7.258)

set_skill("kunlun-shnfa", …)、map_skill("dodge", "shaolin-shnfa") 这样的调用把武功名拼错了:本库没有这个武功文件,有的是拼对的那个。以前这种 NPC 在 create() 里报 No such skill 载入不了;武功缺失时跳过之后它们能载入,可是少了那门武功(set_skill 被跳过,map_skill 映射不到,门派弟子用普通轻功/拳脚)。按核对过的对照表改正,共 12 处、9 个文件:changquana→changquan(4 处)、tiaji-jian→taiji-jian(2 处)、taiji-shenfong→taiji-shengong(1 处)、yan-fly→yanfly(1 处)、lingboweibu→lingbo-weibu(1 处)、jiahua-neigong→jiaohua-neigong(1 处)、zhoangnan-zhi→zhongnan-zhi(1 处)、jinggang-zhi→jingang-zhi(1 处)。(scripts/lpc_fix_skill_names.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 文件。

从 work/ 里删掉 tar 的 PaxHeader/ 记录(2026-10-19,KB 02 §2.2)

压缩包的制作者把 tar 的 pax 扩展头记录(49 path = ...、20 ctime = ...,每个不超过 262 字节)当成文件打进了包里,放在 PaxHeader/ 目录(有的嵌套两层)。转换时 .c 结尾的被改名成 .lpc,在扫描里成了编译不过的“物件”。这些是元数据,不是 mudlib 内容:2 个文件从 work/ 删除(raw/ 里仍然有)。

x != "a" || x != "b" 和 x == "a" && x == "b":同一个值的比较链永远成立/永远不成立(2026-10-20,KB 06 §7.282)

if (arg != "wan" || arg != "丸") return notify_fail(...) 对任何 arg 都为真,dir == "south" && dir == "north" 永远为假:条件下面的守卫要么永远拦、要么永远不拦。作者要的是“既不是 A 也不是 B”(&&)或“是 A 或 B”(||)。!= 链的 || 改成 &&(仅当条件下面是 return 的拒绝/守卫),== 链的 && 改成 ||(scripts/lpc_fix_cmp_chain.py)。改动:/d/mingjiao/shanmen.lpc(1 处)。

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 编码器等少数几处手改)。改动:/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)。改动:/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 个文件)。

不带参数的指令对 0 调用 efun:不在 wiz / adm 目录里的指令(2026-10-21,KB 06 §7.336)

指令敲成不带参数时,驱动把 0 传给字符串参数;下面的指令第一句就把这个 0 交给 efun,运行时报 Bad argument 1 to ...。它们在 cmds/std、cmds/usr、cmds/bos、cmds/app 这类目录里,上一轮的扫描只看巫师目录。用 scripts/lpcc_bare.py 不登录、在一次 lpcc 求值里以 0 调用 main() 逐个确认后,由 scripts/lpc_fix_bare_arg.py --all-dirs 在第一处使用参数的语句前加守卫:没有 help() 的是 return notify_fail("…\n");。改动(1 个文件,括号里是接住 0 的 efun):/cmds/std/actionavailable.lpc(sscanf)。加载检查 HEAD 与工作树对照,没有退步。

注册到不存在函数的 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.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 5 条注册(4 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/adm/npc/referee.lpc(do_halt)、/d/beijing/hg_w.lpc(do_body)、/d/tulong/yitian/npc/obj/ling2.lpc(do_du)、/inherit/misc/fboard.lpc(do_list)。最多的函数名:do_halt 2 处、do_body 1 处、do_du 1 处、do_list 1 处。

对象路径里的 ..:new(__DIR__ "../obj/x") 报 Read access denied(2026-10-22,KB 06 §7.351)

__DIR__ 是当前文件所在目录(带结尾斜杠),__DIR__ "../obj/x" 就是 /d/zhiye/npc/../obj/x;驱动的 legal_path() 不接受带 .. 的路径,new() / load_object() 直接抛 *Read access denied.(实测,find_object() 对它答 0)。走库自己的 carry_object() / 房间 objects 表的路径,库的基类会先规范化,能用;直接把路径交给 new() / clone_object() / load_object() / move() 的不行,调用到那一步才报错。改成规范化后的绝对路径(scripts/lpc_fix_dotdot_dir.py --direct):/d/wudang/npc/xiaocui.lpc(__DIR__ "../obj/nuerxiang" → "/d/wudang/obj/nuerxiang")。

调用了、但这个库自己的守护进程里没有的函数(2026-10-22,KB 06 §7.354)

MACRO->函数() 调用的函数在这个库的守护进程里不存在:call_other() 对不存在的函数不报错、只返回 0,所以这些功能(时间显示、昼夜事件、名字检查、价格文字……)一直没起作用。同一血统另一个库的同名守护进程里有这些函数,原文照抄到本库守护进程末尾(scripts/lpc_port_function.py;调用处传的参数个数、函数体用到的函数 / 全局变量 / 宏 / 字符串键都在本库里有,已核对;守护进程在 HEAD 和工作区都能加载,每个函数用固定参数各调用一次:HEAD 返回 0,现在返回正常的值):/adm/daemons/chinesed.lpc ← hhsj:find_skill()(1 处调用)。

汉字按字节切片的老写法没有换成按字符:改法名 "澄" + name[2..3]、辈分判断 name[0..1] == "澄" 等 15 处(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 的称呼和提示里取到两个字而不是一个字。

本库 15 处(10 个文件):拼名字的赋值 8 处、与汉字常量比较 6 处、to_chinese()[..] 1 处。例:name[0..1] → name[0..0](/kungfu/class/emei/tobebonze.h:40);name[2..3] → name[1..1](/kungfu/class/shaolin/cheng.h:164);chk[0..1] == "你" → chk[0..0] == "你"(/adm/npc/ganjiang.lpc:532)。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:1737)。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 两字同盟名得到 【 兄 弟 】。关键对象加载通过。

中文名 / 验证码仍按 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。

yuanwai 孤儿 call_out announce_success(2026-10-27,KB 06 §7.349 Also)

d/changan/npc/yuanwai.lpc(及 aoxiangtianji 的 d/city 副本)在 check_daughter 里 call_out("announce_success", …),但本文件从未定义该函数;同系也无实现。到点会 *Undefined function*。cry_daughter 已完成认亲与赠书,删除这对 remove_call_out/call_out。证明:扫描 HIT call_out→announce_success 且无定义;改后残渣 0。

深度功能测试(2026-10-27,§6.25 zhongling tell_object 引号错位)

d/wanjiegu/npc/zhongling(及同路径副本)的 tell_object("who, 钟灵脱下一双绣花鞋。\n") 把对象参数写进了字符串;改为 tell_object(who, "…")。证明:原文错位;改后加载,语料残渣 0。

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

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