info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
游戏登录后自报家门为"地狱无门"("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
内容亮点
- 与"地狱"系其它分支一样,是武侠向 MUD,主打门派、装备、拜师成长等 传统玩法。
- 最大的特点是原本是为"指间MUD"这款手机客户端定制的服务端:登录第一 步不是常见的"英文名字"提示,而是一次面向"指间MUD"客户端的版本校验 握手(
crypt挑战应答),用真正的指间MUD客户端连接会自动完成这一步。 - 账号/角色创建信息以特殊分隔符
║拼接成一行整体发送,是这个协议 特有的设计。 - 创角后有一套自己设计的"投胎"仪式:先在阎罗殿选择"猛士/智慧/耐力/ 敏捷/均衡"五种天赋偏属,再跳入"忘忧池"随机洗点,最后从十余个地名 (或"欧阳世家"等四大世家)中选择投胎地点,才能真正获得属性并进入 游戏世界——
score在完成投胎前会正确拒绝显示("还没有出生呐")。
- 更正(2026-08-05):上面提到的"7.68 复活软锁"修复已经撤销——经重新评估,鬼魂"不在场"时放弃复活流程更可能是有意的游戏设计(多数这类档案里鬼魂本身就无法自行移动,离开是一种游荡机制,回来时 init() 会重新触发流程),不是需要强制重试的 bug;详见 NOTES.md。
深度功能测试新发现的 bug(§10.7,2026-08-04)
- §7.68 复活软锁死(第 11 例):
d/death/npc/{wgargoyle, bgargoyle}.lpc(鬼门关的白无常/黑无常)在复活序列进行中如果玩家 暂时不在场就会直接return,永久放弃复活。已按标准修法拆分为 "彻底缺失→放弃"与"暂时不在场→重试"。现场验证:死亡→鬼门关→未被 打断的完整复活序列→成功落地/d/city/guangchang(泥潭广场)。 - 主动检查确认
logind.lpc的user->query("age") == 14食物/饮水 初始化判断查询的是正确对象(user,紧跟user->setup()之后), 不是 §8.9 的错误对象变体;也没有发现 §7.34 的调试输出泄漏。
⚠️ 安全提示:这一"指间MUD"版服务端本身不校验老账号的密码(登录
时会直接用客户端发来的密码覆盖存档里的密码)。原设计把账号认证交给了
"指间MUD"平台,如需公开架设必须自行补上账号密码校验逻辑。
在线试玩
https://mudlibs.fluffos.info/zjdyzj/
管理员账号 / Admin account
- ID:
fluffos密码:Mud@2026中文名: 浮云 - 权限:
(admin)(最高级;通过adm/etc/wizlist授予)。 - 登录需按"指间MUD"协议完成 crypt 握手(详见 NOTES.md):收到
ver1.0,<质询>后回crypt("123456789abcd", 质询[2..3]),再发fluffos║Mud@2026║<校验码>║<邮箱>一行即可。
警告:这是一个公开的默认密码,仅供本地/浏览器试玩。正式对外开服前
请务必修改此密码(并按上文安全提示补上密码校验逻辑)。
本地运行
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
mudlib directory/binary directory: absolute paths tolibs/zjdyzj/work(both, matching the archive's originald:\hellpointing the same directory at both roles).port number: 40074 (per TODO.md's port ledger — 40071-40073 reserved for archives #77-79's concurrent agents).maximum evaluation cost: raised from the archive's original2000000to5000000000, matching the convention already used on sibling ES-II-lineage libs in this project (the stock value is uncomfortably low for this driver's real eval-cost accounting).log directory : /logresolves relative to the driver's own launch CWD (§6), NOT a mudlib-virtual path — createdlibs/zjdyzj/ log/(sibling ofconfig.fluffos) for this. Separately, the mudlib's OWN in-gameLOG_DIR = "/log/"constant (used bylog_file()/write_file()calls in LPC code) IS a mudlib-virtual path, resolving undermudlib directory(i.e.work/log/) — these are two genuinely different directories that happen to share a name; both needed creating. Also createdwork/log/nosave/(§15ah pattern — dozens oflog_file("nosave/...", ...)call sites, includingnosave/register/nosave/security/nosave/logon, none of which had a destination directory in the raw archive),work/binaries/(the configuredsave binaries directory), andwork/adm/tmp/(the configuredswap file's parent directory).- A duplicate, stale
global include file : <global.h>directive (missing thes— no such file exists, onlyglobals.h) appears later in the same config file, inside the "not currently implemented" section. Confirmed harmless and left as-is:read_config()'sscan_config_line()takes the FIRST matching line for any directive and never re-reads it (verified in~/src/fluffos/src/base/internal/ rc.cc), so the correct earlier<globals.h>directive always wins; this stale second line is permanently dead, not a real risk.
Checked, confirmed NOT applicable (per the task's proactive checklist)
- §4 (master.lpc lazy security-daemon
load_object-during-compile recursion):master.lpc'svalid_read/valid_writeonly ever dofind_object(SECURITY_D)(never a lazyload_object()), degrading to a safe default (deny-write / allow-read) if not found. No recursion risk, no fix needed. - §7 (missing
get_root_uid()/get_bb_uid()): both present inmaster.lpcalready. - §14 (
valid_override2-arg vs 3-arg):master.lpc'svalid_override(file, name)already has an explicit special-case forname == "destruct"allowing any file under/adm/simul_efun/(exactly covering theobject.lpc-fragment-inside-simul_efun.lpccase §14 warns about) — this master is more mature than most in this project's catalog on this specific point; no fix needed. - §8d/§15o (
get_include_path()missing):master.lpchas noget_include_path()apply at all, butconvert_lib.sh's automated pass already converted every genuinely-local<x.h>angle-bracket include (14 of them) to quoted"x.h"form, which resolves against the including file's own directory unconditionally (no VM-context/master- apply dependency at all, unlike the angle-bracket path). Since the global<globals.h>(and everything else reached only via the driver's normalinclude directories : /includeconfig default) doesn't need a local-directory lookup,init_include_path()'s fallback to the config's owninc_listwhenget_include_path()isn't defined is sufficient here. Confirmed via a clean boot + full interactive test with zeroCannot #includeerrors anywhere. - §15l (master.lpc
create()destructingSIMUL_EFUN_OB):create()is a bare one-liner (write("master: loaded successfully.\n");), no destruct/reload dance at all. - §15n (custom securityd
valid_readACL blocking driver's own compile-timeload_object/include):securityd.lpc's realdefault_exclude_read/default_exclude_writetables never list"(player)"status in ANY entry (only(wizard)/(arch)/(admin)are ever excluded from anything) — an unauthenticated connection's default(player)status is never denied by this table at all, so the §15n failure mode (ordinary player status blocked from/adm//cmdsmid-connection) cannot occur here regardless offuncvalue. Confirmed by reading the actual ACL data, not inferred. - §8e (
tail()non-efun): zero occurrences anywhere in the lib. - §8f (
TYPE * a, b;forgotten-star): the pattern (object *obs, me;etc, 46 hits) recurs throughout, but spot-checked several — in every case the un-starred trailing name really is later used as a genuine scalar (me = this_player();etc.), not assigned an array anywhere. This is intentional style (declare related vars together), not the Dead Souls "author actually wanted both as arrays" bug. No fix needed. - §15p (DNS/intermud daemon in preload):
adm/etc/preloadDOES list/adm/daemons/network/dns_master— removed proactively per standing policy, before the first boot attempt. - §15ab-2 (DNS/intermud calls inlined outside preload even when excluded from it): grepped for direct
DNS_MASTER/dns_mastercallers beyond preload — found only incmds/usr/who.lpc,cmds/usr/ mudlist.lpc,cmds/adm/shutdown.lpc,cmds/adm/telnet.lpc,cmds/arch/reboot.lpc— all interactive admin/utility commands never exercised by registration or thelook/scoretest, notlogind.lpcitself. Not fixed (out of scope, never reached by the required test). - §15v (
LONELY_IMPROVED/DOING_IMPROVED-gated dead efun calls):DOING_IMPROVEDis referenced in#ifdef DOING_IMPROVED/#elseguards insort_string/sort_msg/file_lines/binary_valid, but is never#defined anywhere in this archive — confirmed every one of these compiles its pure-LPC#elsefallback, already correct and tested by the original author. No missing-efun gap at all. - §15z (
#define nosave staticshim colliding with thestatic→nosaveblanket sed): no such compatibility shim found anywhere. - §15u (dormant destructive "phone-home" anti-piracy function): grepped
securityd.lpc/master.lpcfor suspicious mass-delete/ shutdown-gated functions; none found. - §15r (
check_config.lpc-style fatal driver-assumption self-check inherited into master/simul_efun): no such file exists in this archive. - §15x (hardcoded
MUD_PORTmismatch):include/getconfig.h'sLOCAL_PORT()dynamically queriesget_config(__MUD_PORT__)rather than a baked-in literal — architecturally correct (this is, ironically, exactly the mechanism that turned out to be broken for an unrelated reason, see finding #6 above — a numbering mismatch, not a hardcoded value). - §8g (N-identical-sweep-failures → one shared root cause): applied as methodology throughout the sweep triage above (the
damo.lpcsyntax error and thequest.lpcset_informationtype both resolved 4 and 7 failures respectively via one shared-file edit each, not per-file patches). - §15ag (
ed_start/ed_cmd/query_ed_mode, this driver has__OLD_ED__): zero occurrences anywhere in the lib (grep -r "ed_start\|ed_cmd\|query_ed_mode"empty).feature/edit.lpcexists but uses the realed()efun directly already. - §15ah (missing save-data directory silently aborting a multi-step flow): checked every
log_file()/write_file()target directory lib-wide against what actually exists on disk — only/log/nosave/was missing (created, see config notes above); every other target (/data,/dump,/binaries,/log/user/...) either already existed in the raw archive or is created dynamically byfeature/save.lpc'sassure_file()call before everysave_object().
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):
- **11 files under
adm/daemons/story/*.lpc(anthem,bizhen,jiuyang,sanfenjian,shenzhao,master,pangtong,baguadao,huanyin,jiuzhuan,mengzi) — each is a one-shot "legendary NPC story" scenario whosecreate()callsselect_character(), which filtersall_interactive()for an eligible live player and deliberately self-destructs** (destruct(this_object())) if none is found — completely expected in a barelpcccompile-check context (no players ever connected), confirmed by reading the code (§6b category: "fails only because of missing real-world context, not a real bug"). adm/single/importer.lpc— same shape:create()explicitly requiresthis_player(1)(a privileged/wizard-driven interactive invocation) and self-destructs otherwise. Confirmed by reading the code.adm/daemons/network/messaged.lpc— theget_config()index-mismatch bug (finding #6 above); genuinely fails to load when directly compiled+created, but never reached in real play anyway (wrong preload path, finding #7). Deliberately left unfixed, documented above.
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)
- LPC formatter applied to all
.lpcunderwork/(7,302 total, 7,272 written, 25 unchanged, 5 self-checked errors left untouched). Spot-checkedadm/daemons/logind.lpc'scrypt(ZJKEY, "zj")fix post-format — intact, unchanged in substance. - Native re-test against the freshly rebuilt driver (
~/src/fluffos/build-debug/src/driver): clean boot, only the pre-existing benignversiond.lpc/socket_bind()line indebug.log. Sincemudclient.py's static--sendqueue can't compute the dynamic crypt-challenge response, wrote a small one-off Python client (cryptstdlib module) that: connects, parsesver1.0,<challenge>from the banner, computescrypt(ZJKEY, challenge[2:4])and sends it back, then sends theid║password║ crypt(ZJKEY,id)+crypt(ZJKEY,password)║emailaccount line and agender║avatar║namecharacter-creation line. Verified end-to-end with a fresh id/real Chinese name (zjretest/秦岭): "版本验证成功" (version verified) → account accepted → character created → landed in 世外桃源 (地狱无门),look/score(correct pre-投胎 message)/quitall produced correct output. Confirmed thecrypt(ZJKEY, "zj")fix is fully deterministic run-to-run (the challenge string and its[2:4]salt slice were byte-identical across every connection tested in this pass, as expected now that the explicit 2-char salt replaced the oldcrypt(key, 0)call) — this determinism is what makes a scripted test client (or a real client that recomputes the response once) viable at all. - WASM test: attempted via
scripts/wasm_client.jswith the crypt-response precomputed as a literal--sendline (viable exactly *because* the challenge is now deterministic — no need for a reactive client). Never reached the crypt banner at all — root cause is the SAME test-harness gap identified onzjdyarylin this same batch, an independent occurrence:clone/user/login.lpc'slogon()unconditionally callslog_file("nosave/logon", ...)(log/nosave/— note this is this lib's post-static→nosave-sed directory name, a REAL directory shipped inwork/log/nosave/on disk, confirmed) as its very first statement, nocatch().scripts/wasm_client.js'scopyDir()only recreates the barelog/directory itself in the in-memory FS, not real subdirectories likelog/nosave//log/static//log/user/— so this throwsWrong permissions for opening file /log/nosave/logon for append. "No such file or directory"uncaught, andnew_conn_handler()disconnects the connection beforelogon()ever writes thever1.0,...challenge. Already flagged inzjdyaryl's NOTES.md for the orchestrating session to fix in the shared harness (not done here myself, since it's a script other agents' concurrent sessions use). Separately: this lib'slogon()also has an unguardedVERSION_D->is_version_ok()call later in the function (same shape aszhonghua2/zhongjidiyuin this batch), which — given this lib'sversiond.lpcalso uses rawsocket_*()calls unavailable under WASM — would likely block login a SECOND time even after a harness fix for thelog/nosave/gap. Both are WASM-sandbox/harness artifacts, not mudlib bugs (native completes the full crypt-handshake + registration + gameplay flow cleanly, verified above) — not patched, per the standing instruction for WASM-mode limitations. (The VERSION_D gate is now patched — see the 2026-07 pass below.)
WASM-enablement pass (2026-07): loopback gates + admin seeding
Standard pass per AGENTS.md §1.3b/§1.3c/§1.3e/§1.5:
adm/daemons/band.lpc: newis_local_site(site)helper;is_banned()returns 0 for loopback/empty/malformed input. (Note:logon()passesquery_ip_name(), not the number — a resolved hostname also passes the local check; acceptable for the WASM-first use case.)adm/daemons/logind.lpc:
- 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).
adm/daemons/network/messaged.lpccreate():catch(startup_udp())— the §15aj get_config port bug makessocket_bind()throw a string- type error if this daemon is ever loaded (it IS lazily loaded bycheck_ok()despite the broken preload path); now degrades gracefully instead of aborting the login that triggered the load.adm/daemons/closed.lpcheart_beat()/adm/daemons/questd.lpcstart_all_quest():find_object(VERSION_D)guards (both preloaded).- No uptime()/anti-flood/per-IP gates exist in this lib's zjmud login flow — nothing else to bypass. The crypt version-handshake itself is already deterministic since the earlier
crypt(ZJKEY, "zj")fix and is left in place (it is the edition's defining feature; scripted clients can compute the reply — see/tmp-style client recipe in the transcript sections above, ZJKEY123456789abcd, replycrypt(ZJKEY, challenge[2..3])). - Admin seeded:
fluffos/Mud@2026/ 浮云, rank(admin)viaadm/etc/wizlist. Registered via the REAL zjmud flow (handshake →fluffos║Mud@2026║crypt(ZJKEY,"fluffos")+crypt(ZJKEY,"Mud@2026")║ [email protected]→男性║1║浮云). Verified after wizlist+restart: first login shows 目前权限:(admin) andupdate /d/register/entry.lpc→ 成功. - Security note (pre-existing design, documented not changed): this edition performs NO server-side password verification on existing accounts —
get_user()restores the login save and then OVERWRITES the stored password with whatever the client sent (verified empirically: a wrong-password login attempt succeeds). Account authentication was evidently delegated to the 指间MUD platform upstream. Anyone who can speak the ZJKEY handshake can log into any account, includingfluffos— flagged in the README's hosting warning. - Retest: fresh registration (
regtest/秦风) end-to-end into 世外桃源, look/score(pre-投胎 message)/quit correct; test saves removed. Final debug.log: only the two known socket_bind lines (versiond uncaught ×1, messaged now-caught ×1).
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 类):
adm/daemons/logind.lpc:872的user->query("age") == 14判断 ——确认不是 §8.9 的错误对象变体:查询对象是user(角色的持久 存档对象),且紧跟在user->setup();之后,和已归档的正确写法完全 一致,不是判断连线对象ob。食物/饮水会在这一刻正确初始化。- 全文搜索
printf(——logind.lpc及d/register/全部注册/创角 文件均无遗留调试输出,没有 §7.34 的实例。 feature/command.lpc:40的command_hook已经是nomask int command_hook(...)(代码里的注释显示这是此前某一轮已经 修复过的),本轮确认依然干净。adm/daemons/logind.lpc:418的MESSAGE_D->find_chatter(...)调用 已经包在catch()里,不是未加防护的裸调用。
新发现并修复:第 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 崩溃)
BULLETIN_BOARDinherit+ 多余replace_program()致命形状(AGENTS.md §7.86,post命令崩溃):全档案 44 处命中,已删除多余的replace_program(...)调用(保留inherit),逐文件保留原有行尾格式(CRLF/LF 按文件原样)。本次为跨库 §7.86 扫描修复(触发原因:该 bug 已在 6+ 个互不相关的血统家族独立确认,属于近乎普遍的拷贝粘贴模式),仅做编译检查(驱动干净启动、端口正常监听),未做完整 §10.7 深度游玩测试。
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 同源的三处编译警告
include/npc/indian.lpc与masia.lpc共用全局名ma_sname/ma_pname;两个头文件被同一个守护进程(npcd等)一起包含时,后包含的印度名表覆盖马来名表,generate_ma_name()生成的是印度名字。印度表改名in_sname/in_pname(KB 04 §6.10)。cmds/std/team/swear.lpc:结义失败提示被多余的;截成两句,fail只剩"……可惜现在有人不在,$N",后半句"的提议只好作罢。"丢了;接回去。
验证:改动的文件和包含 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):“无副作用表达式”警告复查
cmds/wiz/who1.lpc:输出数组建好前一行孤立的分隔线字符串(与 wxddym 同码),删去,行为不变。载入无回退。
深度功能测试(§10.7,2026-10-12):编译警告收尾
能编译的文件里的警告 3 -> 0(toolkit_fix.sh zjdyzj 扫描:7289 个文件可编译,0 条诊断)。与 nitan170911/wxddym 当天修过的同一批文件,照它们的做法手工改;ZJ 手机客户端登录协议代码没有动。
真问题:
clone/misc/swmuding.lpc神木王鼎:自己声明了一张mapping insects(13 种毒虫,含 heiguafu/wucaizhu/bingcan 等),可读这张表的是基类clone/misc/muding.lpc的函数,它们读的是基类自己那份(8 种虫的普通木鼎表),叶子的表从来没被用过,神木王鼎引来的虫和普通木鼎一样。表改名sw_insects,在create()里赋给基类的insects。
其余:
feature/food.lpc、feature/liquid.lpc的do_effect()由void改为varargs int do_effect(object me)、末尾return 1;:cmds/std/eat.lpc/drink.lpc对only_do_effect的物品用return ob->do_effect(me);当命令结果,重载它的d/guanwai/obj/wan.lpc、d/xiakedao/obj/pigpart.h都是int,基类与之同型后两条返回类型警告消失。
验证:改动文件、所有继承 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… 行。