info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
由 "XiaKeXing Gaming Group" 制作的武侠 MUD,登录画面自称 1996-2003 年出品,属于早期"ES II"引擎家族的一支,与本项目中的 `rzrmud`(大唐西游)、`bmxkx2001`、`xuanjianlu`(玄剑录)等同属一个更古老的技术谱系,但游戏世界内容各自独立(发行版本层面,`d/taihu/gumu/houtang.lpc` 等场景文件头带"Cracked by Roath"署名,和 `bmxkx2001`/`jym`/`xkm` 是同一批流通版本,`xuanjianlu` 则属于"Cracked by Kafei"的另一批流通版本,与 `xkx2000zxb` 同批,两批文件头署名不同但核心引擎同源)。游戏以"侠客岛"为背景:新角色先在岛上沙滩苏醒,由"赏善使"张三或"罚恶使"李四迎接并带往登记处办理入岛手续(用 `register <邮箱>` 完成注册,系统随机生成新密码并断开连接,是一种老式的防小号验证机制),之后才正式踏入江湖,探索少林、武当、华山、丐帮、星宿派等经典金庸门派地图,体验完整的门派拜师、内功修炼、江湖任务与帮派系统。
English
Produced by the "XiaKeXing Gaming Group," with a login screen claiming a 1996-2003 pedigree. Part of an early branch of the ES II engine family, sharing a more distant common technical ancestry with this project's rzrmud (Great Tang Journey West), bmxkx2001, and xuanjianlu (Record of the Mysterious Sword), though each has an independent game world — at the release level, scene files such as d/taihu/gumu/houtang.lpc carry the "Cracked by Roath" header shared with bmxkx2001/jym/xkm, while xuanjianlu belongs to a separate "Cracked by Kafei" batch (shared with xkx2000zxb); the two batches differ in header credit but share the same core engine. Set on "Xiake Island," new characters wake on the island's beach and are greeted by the "Rewarding Envoy" Zhang San or the "Punishing Envoy" Li Si, who guide them to registration before they set out into the jianghu proper. The world covers classic Jin Yong sects — Shaolin, Wudang, Huashan, the Beggars' Sect, the Xiuzhong Sect, and more — with a full apprenticeship, inner-cultivation, quest, and clan system; new arrivals must first follow a welcoming NPC to the island's registry and complete `register <email>`, a vintage anti-multi-accounting mechanism from the era.
README
内容亮点
- 以"侠客岛"为背景的武侠文字 MUD:新角色先在侠客岛的沙滩上苏醒,由 岛上的"赏善使"张三或"罚恶使"李四迎接并带往登记处办理入岛手续,之后 才正式踏入江湖。
- 游戏世界涵盖少林、武当、华山、丐帮、星宿派等经典金庸门派地图,包含 完整的门派拜师、内功修炼、江湖任务与帮派系统。
- 新手接引流程颇具特色:登岛后只允许极少数指令,需跟随迎宾 NPC 走到 "侠客岛挂名处"用
register <邮箱>完成登记,系统会生成一个随机新 密码并断开连接——这是老式防小号验证机制,用新密码重连即可继续。
在线试玩
https://mudlibs.fluffos.info/xkx2001/
管理员账号 / Admin account
- id:
fluffos - 密码 / password:
Mud@2026(已通过游戏内passwd指令将注册流程随机分配的密码改为该标准密码。) - 中文名 / display name: 浮浮
- 权限 / level:
(admin)—— 本 lib 最高权限。授权数据存放在data/securityd.o(wiz_status+wiz_sites,后者必须有.*之类的 登录地址模式,否则巫师账号无法登录)。
警告:这是一个公开的默认密码,仅供本地/浏览器试玩。正式对外开服前
请务必修改此密码(游戏内 passwd 指令)。本地运行
cd libs/xkx2001
~/src/fluffos/build-debug/src/driver config.fluffos游戏端口:40021。
NOTES · 移植与修复记录
xkx2001 — 侠客行 Ⅰ (Xiakexing/The Quest of Oriental Chivalry, 2001 test lib)
Archive: xkx2001测试用老lib.zip. Port: 40021. Status: done (boots
clean, connects, plays through login into "create new character?"
confirmation with zero crashes).
What this is
"ES II mudlib" lineage (comment header in adm/single/master.c: "for ES
II mudlib... modified by Xiang for XKX"), same family as rzrmud
(archive #23, "大唐西游") — shares the exact same master.c function set
(connect/epilog/preload/log_error/valid_*/standard_trace/
error_handler) almost verbatim, both apparently descended from the
same "XKX"(侠客行)-authored base. adm/single/master.c +
adm/single/simul_efun.c layout, same as bxsj/bxsj1 (archives #4/#5)
— but NOT the same lineage as those (checked: no log_error()/
CHANNEL_D reentrancy call, no lazy-load-in-valid_read pattern, 2-arg
valid_override but no allow-list issue hit in testing). MudOS v22b25,
"XiaKeXing Gaming Group 1996-2003".
Fixes applied
None — booted clean with zero fixes on the first attempt (mechanical
convert_lib.sh pipeline only), same as rzrmud. Before starting,
checked this lib's master.c against the known bxsj-lineage bugs (§4
CHANNEL_D reentrancy, §14 valid_override 3-arg, §8c this_player()-override
footgun) since the adm/single/ path layout matches bxsj/bxsj1 — none of
those specific bugs are present in this master.c despite the similar
directory layout, confirming layout similarity alone doesn't mean shared
bugs; the actual master.c *content* here is closer to rzrmud's.
Known content gap (not fixed — see AGENTS.md §13)
lpcc sweep: 8124 files, 6884 pass / 1240 fail (84.7%). Largest cluster
(52 failures, one root cause per AGENTS.md §8g): inherit/misc/
bboard.lpc (and its .bad.lpc backup copy) calls EDITOR_D->add(...)/
EDITOR_D->get_file_num(...) — a whole daemon (EDITOR_D, apparently an
"anthology/featured-posts editor" service for bulletin boards) that
doesn't exist anywhere in this archive at all, not a typo or missing
#define. Genuine missing content (an entire daemon file), affecting
only the bulletin-board "compile into archive" feature — not fixed, out
of scope. The remaining ~1188 failures are the usual long tail (illegal-
character/encoding edge cases, syntax typos in individual files,
#include-only fragments failing standalone per §6b) — not triaged
individually.
Interactive test result
Connects, ASCII banner + mudlist status table render correctly (Chinese intact), GB/BIG5 charset prompt works, username length/alphabetic validation works correctly (rejected both a too-long and an all-Chinese attempt before accepting a valid one), and a valid new username reaches the "create new character?" (y/n) confirmation prompt with zero crashes. Did not create a full character or play further (out of scope).
Re-verification pass (QA sweep, later session)
Earlier passes never tested past the "create new character?" confirmation prompt / the password-setup step, so the full registration -> post-login command flow (per the project's standing §15ae policy) had never actually been exercised here. Re-tested end-to-end this pass and found two real, fixed bugs, plus clarified one thing that looked exactly like a bug but wasn't:
1. §15s — adm/simul_efun/message.lpc's tell_room() passed a raw,
unset int 0 as message()'s 4th ("exclude") argument whenever
called in its common 2-arg form. This driver's message() rejects that
shape (Bad argument 4 to EFUN message() ... Got: int(0)), and since
tell_room() fires from a room's own create()/reset()/
make_inventory() chain (an NPC's arrival announcement) the very first
time ANY room is lazily compiled, the uncaught error aborted the
in-progress move() call that placed a brand-new character into their
starting room -- leaving the character with no environment at all
(look showed "你的四周灰蒙蒙地一片,什么也没有", environment(me) was
0, every subsequent command touching environment() crashed). Fixed
with the standard pattern: exclude || ({}). Re-verified: after the
fix, move() into the starting room completes cleanly and look
renders the real room every time.
2. §15af — master.lpc's log_error() called wizardp(this_player(1))
unconditionally, including during PRELOAD when no player is
connected (this_player(1) is 0) -- wizardp() calls into
SECURITY_D via call_other, which can lazily load_object() the
security daemon; doing so while the driver is still mid-compile of
some unrelated file throws Object cannot be loaded during
compilation. (seen 23 times on a clean boot, all during preload, all
non-fatal here but pure log noise plus latent-crash risk of the kind
documented for shenzhou). Also fixed the same function's
"Warning:" vs the driver's real lowercase "warning:"
case-sensitivity bug while touching this line (the filter never
actually matched anything before). Fixed by guarding the whole check
behind this_player(1) && first. Re-verified: a clean boot now shows
zero 执行时段错误 lines at all (was 23).
3. Missing /log/nosave/ directory (§15ah-shaped): securityd.lpc's
own denial-logging (log_file("/nosave/WRITE_LOG", ...)) and
message.lpc's read/write audit log both target this subdirectory,
which the raw archive never shipped. Created
libs/xkx2001/work/log/nosave/ proactively.
4. Not a bug -- clarified a red herring that looked exactly like a
silent-command-dispatch bug (§15ae's symptom shape) but has a totally
different, legitimate cause: every brand-new (non-wizard) character
lands on the "侠客岛" landing beach (d/xiakedao/shatan1.lpc), whose
own init() calls add_action("block_cmd","",1) -- a DELIBERATE,
hand-written onboarding gate that blocks every command except quit,
goto, suicide, follow, tell, say, reply, look until the
player follows the greeter NPC (auto-drags them after ~15s regardless)
to the "侠客岛挂名处" (registration desk) and types
register <email> -- which the mudlib code implements as generating
and returning a brand-new system password and disconnecting the
session (an authentic 1990s-era anti-multi-accounting mechanic, not a
crash). block_cmd() returns 1 (block) with no message at all for
anything outside that whitelist, so score/inventory/help/who/
etc. silently doing nothing at this stage is completely expected
in-game behavior, not a driver-compat regression -- confirmed by
reconnecting with the new password afterward and finding look/
score/quit all work perfectly in the real starting room. Spent
real diagnostic effort here (temporary efun::write_file() tracing in
process_input()/command_hook()/find_command(), all removed
afterward) before finding shatan1.lpc's block_cmd() -- worth
flagging in AGENTS.md as a new variant of the "commands silently do
nothing after registration" family (distinct from §15ae's private-
command-hook cause and §15aj's missing-environment cause): a
deliberate, narrow post-registration command whitelist gate with a
silent (no-notify_fail) block response.
Full clean re-verification (fresh boot, after all fixes): registration
with real Chinese names 秦云(female)/秦伟(male), through the
follow-NPC -> auto-escort -> register <email> -> new-password ->
reconnect -> look/score/quit flow, zero 执行时段错误 in
debug.log across the entire session.
Post-hoc fix: UTF8-native is_chinese/registration (AGENTS.md §15h)
Applied in a later batch pass across the whole project: is_chinese/is_chinese2
in the shared chinese.lpc simul_efun fragment used GBK byte-range checks that
silently never match real Chinese text once strings are UTF-8 (this driver's
str[i] returns a Unicode codepoint, not a GBK byte). This broke character
registration specifically -- any real Chinese name was rejected. Fixed the
range check to test the CJK Unicode block instead, and halved the
GBK-byte-calibrated length bounds in check_legal_name to match. See
AGENTS.md §15h for the full writeup; confirmed via a real interactive
registration test (Chinese surname + given name reaching the next prompt).
Driver-rebuild retest + LPC reformat + WASM pass (this session)
- LPC formatter applied (
tools/lpc-syntax, allwork/*.lpc): 8,074 files reformatted, 46 unchanged, 9 refused (self-check failures on messy legacy code, expected). Confirmed the §15stell_room()fix inadm/simul_efun/message.lpcsurvived reformatting intact. - Native re-test against the freshly rebuilt driver (
~/src/fluffos/build-debug/src/driver, rebuilt from latest upstream master): boots clean, zeroFATAL/SIGSEGV/执行时段错误indebug.log. Full registration verified end-to-end with real Chinese names, including this lib's distinctive onboarding mechanic: id/name/ password/gift/email/gender -> lands on the "侠客岛" beach -> greeter NPCfollow-escorts the player (auto-drags after a delay) to the "侠客岛挂名处" (registration desk) ->register <email>generates a brand-new system password and disconnects -> reconnect with the new id+password ->look/score/quitall correct in the real starting room. Verified with idqretkxr/real name 秦云归来 (male), new passwordychifissued byregister, reconnect succeeded and reached the beach's continuation room with correct character sheet. - WASM build tested (
~/src/fluffos/build-wasm/srcviascripts/wasm_client.js): boots cleanly (only expected non-fatal preload warnings, e.g.Macro 'ROOM' redefinedand no-sockets- package notices). Registration succeeded end-to-end under WASM, including the full beach -> follow -> register-desk -> new-password flow — real Chinese name 秦云网四 (idqfwaskc),registercorrectly issued a new password (slrze) and disconnected, exactly matching native behavior. Contrary to this project's general expectation for this lib (this lib's own login/ban checks —BAN_D->is_banned(query_ip_name(ob))andREGBAN_D->is_banned(query_ip_name(ob))inlogind.lpc— usequery_ip_name(), notquery_ip_number(), and neither gated the registration path in this pass's testing), this lib's own registration flow was NOT observed to be blocked by the documentedquery_ip_number()-format WASM limitation in the portion tested. The one step not further exercised under WASM: reconnecting with theregister-issued new password to complete the loop, sincewasm_client.jsboots a fresh in-memory filesystem per invocation and has no built-in way to reconnect to an already-disconnected session within one process (a harness limitation, not a mudlib or driver bug) — the native re-test above already fully covers that final step. The generalquery_ip_number()WASM limitation described in AGENTS.md remains real and does affect other libs in this batch (seexyzxfk/xyzxfy2's NOTES.md, whoseBAN_Dchecks usequery_ip_number()directly) — it just doesn't reach this particular lib's tested registration path.
WASM-enablement pass (loopback-allow + admin seed)
Applied the standard WASM-first changes (AGENTS.md §1.3b/§1.3e/§1.5):
1. Loopback always allowed through ban gates — this lineage gates on
query_ip_name() (hostname), not the IP:
- adm/daemons/band.lpc: added is_local_ip(string site) helper
(empty/non-string, localhost, 127.* => local) and
short-circuited is_banned() to return 0 for local sites.
- adm/daemons/regband.lpc is_banned() (the registration-ban gate
called from logind.lpc before new-character creation): same
inline local short-circuit. (regband is also matched against email
patterns; the local check only matches localhost-shaped strings so
email matching is unaffected.)
2. Uptime startup gate: none in this lib.
3. Anti-flood throttles: none per-IP (MAX_USERS is a global cap —
left intact). SECURITY_D->valid_wiz_login() requires a wiz_sites
entry for every wizard — fluffos is seeded with pattern .* so this
gate passes from anywhere including WASM garbage IPs; gate itself
left intact.
4. Admin account seeded — id fluffos, display name 浮浮 (male).
Registered through the REAL flow including this lib's quirks:
initial password at creation, landing on the 侠客岛 beach, escort to
侠客岛挂名处, register [email protected] which ASSIGNS A RANDOM
password (was zfbod) and disconnects. Then granted (admin) by
editing data/securityd.o (the lineage's ACL save data — NOT the
adm/etc/wizlist.h file, which this securityd only uses as a
fallback when the save is absent): added
"fluffos":"(admin)" to wiz_status and "fluffos":".*" to
wiz_sites (required by valid_wiz_login). After restart, logged
in and used the game's own passwd command to change the random
password to the standard Mud@2026. Verified: 目前权限:(admin),
update /adm/daemons/band recompiled OK, wizard-view look shows
file paths. (goto on a non-room daemon object is a no-op in this
lib; update is the canonical ACL check.) Save files:
work/data/user/f/fluffos.o + work/data/login/f/fluffos.o
(untracked, NOT gitignored — orchestrator must git add), plus the
modified work/data/securityd.o (already tracked).
Retest: fresh registration (fluffos itself) through beach → 挂名处 →
register → random password → re-login all worked natively; fluffos
re-login with Mud@2026 + update verified; debug.log free of runtime
errors.
深度功能测试 / Deep functional test (round two)
First genuinely hands-on *playthrough* pass (all prior passes above
verified registration/admin-login/WASM boot, never actual post-登记
gameplay: skill learning, sparring, sect contact, or a real
disconnect/reconnect). Read doc/help/newbie.dec99 and doc/help/intro
in full first — both correctly describe this lib's mechanics (fight
is the safe, non-lethal sparring form; kill/hit are real; bai/xue
for apprenticing/learning; wimpy for auto-flee) even though the
specific starting geography they describe (扬州客店 as the general hub)
turned out to belong to the shared "ES2 newbie doc" template, not this
lib's actual map — this archive's real starting zone is entirely the
self-contained "侠客岛" (Xiake Island) tutorial/quest zone
(d/xiakedao/, ~100 rooms), reached via the beach → escort → 挂名处 →
register flow already described above. Native driver
(~/src/fluffos/build-debug/src/driver config.fluffos), scripts/
mudclient.py throughout. Found and fixed one serious bug (a real
driver segfault, not just a caught LPC error — the first of that
severity found in this project's round-two pass) plus hardened the same
function against a second, non-fatal failure mode found while
re-verifying the first fix.
Test characters left behind (small save files, kept as playthrough evidence, matching this project's standing convention):
- id
qinxia, 秦霞 (female), passwordgzctc(system-issued byregister, original self-chosen password wasXia@2026but the in-gameregisterstep always overwrites it with a random one — see the onboarding flow documented earlier in this file). Main playthrough character: completed the full 沙滩 → escort → 挂名处 →register→ new-password reconnect flow; learned 基本内功 (force) to level 1 via the organic teacher-NPC path (蓝衣弟子/diziat 瀑布, a non-sect basic-skills trainer reached vianorthupfrom 望海亭); fought a real, safefight dizisparring match to conclusion (气 dropped visibly, match ended cleanly with "承让", no death risk, confirming this lib's own safe-sparring mechanism —inherit/char/ npc.lpc'saccept_fight()— works exactly asdoc/help/newbie.dec99describes); received a free 短剑 from 黄衣大汉 (han) viaask han about 武器; attemptedbai lingxiao(凌逍, a real 华山 sect member reachable directly on the island,create_familyconfirmed) and was correctly declined with "要拜师,你得去拜我师父" (game design: a generation-14 disciple can't take his own apprentices, redirects to his own master) — confirms the sect-join code path is live and reachable from the island itself, not just via off-island travel. Note: theforceskill progress above did not survive the driver crash described below (unsaved at the time) — re-verified the teacher-NPC learning mechanism works but did not re-grind it back to level 1 after the restart, not worth the tool-time. - id
cetest, 测试 (female), passwordkbzeh— a disposable second character used specifically to rule out a suspected registration- reconnect race (see Bug 2 below): repeated register → reconnect cycles with careful, non-overlapping connections all completed cleanly with no duplicate-login prompt. - id
linxue, 林雪 (female), passwordnybhj— used specifically to reproduce and re-verify Bug 1 (the crash) end to end with a real player, deliberately left un-followed through the full escort route (d/xiakedao/npc/longx.lpc'smove_next(), all 9 scripted steps: 沙滩 zone → 小路 → 迎宾厅 → 瀑布 area → 甬道 → 大山洞) long enough for the guide NPC to force-drag her (check_follow's count>2 branch) and for the fixed retry path to run repeatedly for several real minutes with zero crashes (see Bug 1's verification below).
Bug 1 (NEW bug class): a mudlib logic bug (duplicate call_out scheduling from a missing return) segfaults the entire driver process, not just an LPC-level caught error
File:line: d/xiakedao/npc/longx.lpc, move_next(), ~line 188-194.
- Symptom: the driver process itself died —
Connection refusedon a subsequent connect attempt, no LPC-level error visible to any player, the crash only discoverable viadebug.log's C++ backtrace and the process actually being gone fromps. This happened spontaneously during an idle real-time wait (~2.5 minutes) while two test characters (an admin account and a fresh player) were sitting, one of them still mid-escort by this lib's own island-guide NPC (d/xiakedao/npc/longx.lpc, randomly named "龙一".."龙九" per instance — the exact same NPC that escorts literally every brand-new character from 迎宾厅 toward the island's interior after registration — see this file's own onboarding-flow section above).debug.log's crash backtrace bottoms out inside the driver's own C++call_out()implementation (src/packages/core/call_out.cc:209,while (ob->shadowing) { ob = ob->shadowing; }) — a null/danglingobject_t*dereference consistent withcall_out()being invoked for an already-freed object. - Root cause:
move_next()walks the guide NPC through a fixed script of movement commands (commands[]/places[], 9 steps) via a self-reschedulingcall_out("move_next", 10, me, count). When the escorted player becomes separated from the guide mid-step (!present(me, environment(long))— happens routinely: some steps in the script are non-directional verbs likeclimb tree/jump fallthat this driver's automatic leader/follow co-movement doesn't propagate to followers, so the two can legitimately end up in different rooms even with both still fully connected), the recovery branch explicitly re-teleports the player and reschedulescall_out("move_next", 10, me, count)with the current, unchangedcount(a retry of the same step) — but the code had noreturnafter that reschedule, so execution fell straight through into the function's own unconditional tail, which reschedulescall_out("move_next", 10, me, count+1)a second time on the same object in the same invocation. Every time this branch fires, one MORE duplicate/orphaned "move_next"call_outentry accumulates against the same NPC object; if the object is laterdestruct()ed (e.g. the ordinary "guest gave up and wandered off" cleanup path, lines 163-169) while one of these un-cancelled duplicates is still pending, the driver's own C++ call_out bookkeeping is left holding a scheduled callback against a now-freed object — which segfaults the whole process, not just the one LPC call, the next time that duplicate fires. - Why every earlier verification layer missed this: it requires (1) a real player actually being escorted by this NPC (only happens post-registration, past every smoke test's usual stopping point), (2) the player becoming separated from the guide at least once (routine during real play, essentially never during a scripted
--sendsequence that never idles mid-route), and (3) enough real wall-clock time (multiple 10scall_outcycles) for the duplicate-pileup + destruct race to actually collide — a boot-log watch or alook/score/quitsmoke test never lingers anywhere near this NPC long enough to hit it. Found purely by accident during this pass's required real-time wait for the net-dead/reconnect check below (two characters were left idle mid-escort while waiting) — exactly the kind of thing §10.7's "wait a real amount of time" step exists to catch. - Fix: add the missing
returnso the retry-reschedule path can never fall through into the unconditional one:
``lpc
// BEFORE:
me->move(places[count]);
remove_call_out("move_next");
call_out("move_next", 10, me, count);
}
count = count + 1;
remove_call_out("move_next");
call_out("move_next", 10, me, count);
return;
}
// AFTER:
me->move(places[count]);
remove_call_out("move_next");
call_out("move_next", 10, me, count);
return; // <-- was missing; this is what let the branch fall
// through into the unconditional reschedule below,
// double-scheduling "move_next" on the same object.
}
count = count + 1;
remove_call_out("move_next");
call_out("move_next", 10, me, count);
return;
}
``
- Verified live, twice: (1) restarted the driver clean, waited a real ~2.5-3 minute idle window with two characters mid-escort by this exact NPC (reproducing the same conditions as the original crash) — the process stayed up the whole time,
ps/ssconfirmed it was still listening,debug.logstayed free of new fatal/segfault lines. (2) A second, harder stress test: deliberately walked a fresh character (linxue) into 迎宾厅 and left the connection open, un-followed, for the guide's full 9-step scripted route (several real minutes) — the!presentrecovery branch fired repeatedly (visible as "龙七不知从哪变了出来,拉起你的手边走边埋怨道" printing many times in a row near the route's end), i.e. the exact vulnerable branch ran dozens of times back-to-back under the fix, and the driver never crashed.update /d/xiakedao/npc/longx(as the seeded admin account) recompiled the fixed file with zero errors afterward. - Residual, lower-severity issue found while stress-testing the fix (not a crash, not separately fixed as a distinct bug, folded into the same edit below): at the route's very last step (
places[8]=/d/xiakedao/dadong, a room whose only exit issouth), the retry branch can loop indefinitely (every 10s, "拉起你的手边走边埋怨道..." repeating forever) instead of ever reaching the intended "请在这里稍候, 岛主一会便到" completion line — the guide NPC just never finishes greeting that specific player, though it also never hurts anything (no error, no leak beyond the one harmless retrycall_out). Not root-caused further given time budget; noted here rather than silently dropped. - A second, related failure mode found and fixed while investigating:
move_next()'scommand(commands[count])call (the line right before the buggy branch above) can itself throw an uncaught driver-level error — reproduced live, twice, with the exact same lib-shipped message both times:*Illegal to move or destruct an object (/d/xiakedao/yongdao3) defining actions from a verb function(north) ... which returns zero(this driver's own safety net against destructing/moving an object still on a verb-function call stack — a pre-existing quirk of this specific room transition, not introduced by anything in this pass; not further root-caused). Before Bug 1's fix, this was moot (masked by the crash). After Bug 1's fix, this alone would still silently abortmove_next()before it ever reaches the reschedule logic — leaving that guide NPC instance permanently stuck with no pendingcall_outat all (harmless, but a content regression: that NPC just stops working for that player). Wrapped the call incatch()so a failed scripted step degrades to the same "guest got left behind, drag them back" recovery path instead of derailing the whole function:
``lpc
// BEFORE: command(commands[count]);
// AFTER: catch(command(commands[count]));
``
Re-verified: the identical "Illegal to move" line recurred a second
time during the stress test above (same room, same step) and this
time did not abort the guide's schedule — it simply fell into the
ordinary "拉起你的手" recovery branch like any other separation,
which is the intended degraded behavior.
- Lineage check:
libs/bmxkx2001/work/d/xiakedao/npc/ longx.lpcis a byte-for-byte identical copy of the pre-fix file (confirmed viadiff) — same missingreturn, same vulnerable shape, almost certainly equally capable of segfaulting that lib's driver under the same real-time-wait-mid-escort conditions. Not fixed there (out of scope for this pass per the task instructions — flagged for the orchestrator/a future pass on that lib). This bug is a mudlib-source logic bug (missingreturn), not a driver-generic pattern by itself, but the *consequence* (an LPC-reachable path to a driver segfault viacall_outscheduled against a soon-to-be-freed object) is a driver-generic risk worth watching for anywhere a self-reschedulingcall_outchain has more than one reschedule call reachable from the same function body — see the draft AGENTS.md class in this task's final report.
Bug 2 candidate investigated, NOT confirmed as a real bug (documented so the next pass doesn't re-chase it)
While repeatedly registering/re-registering the same test id in quick,
overlapping succession early in this pass (deliberately messy,
back-to-back connections, several left to time out mid character-
creation), one register → immediate-reconnect sequence surfaced the
"您要将另一个连线中的相同人物赶出去,取而代之吗?" (duplicate-login)
prompt where a clean netdead-reconnect was expected — looked exactly
like regid.lpc's register_char()'s destruct(body) (on the very
object that is itself the current interactive session) not actually
freeing the id in time for a fast subsequent reconnect. Investigated at
length (traced enter_world()/reconnect()/find_body() in
logind.lpc, net_dead()/reconnect() in clone/user/user.lpc and
clone/user/login.lpc). Could not reproduce it under a controlled,
single-character, non-overlapping test (cetest: register → new
password → immediate reconnect, repeated cleanly multiple times, always
a clean netdead-reconnect, never the duplicate-login prompt). Current
conclusion: the one observed instance was almost certainly an artifact
of the deliberately chaotic overlapping-connections test pattern used
earlier in this pass (multiple abandoned in-flight LOGIN_OB
registrations for the same id) rather than a reproducible bug in the
real register/reconnect path — recorded here, not filed as a bug, so a
future pass doesn't need to re-investigate from scratch.
Also verified working, no bugs found
- Net-dead (unclean) disconnect + reconnect, both prompt and after a real wait: reproduced constantly and incidentally throughout this pass (every
mudclient.pyinvocation in this session that didn't end in an explicitquitis itself an unclean disconnect) — every single reconnect, including several after multi-minute real waits and one across an actual driver crash/restart (which, expectedly, reverted to each character's last explicitly-*saved* location rather than their live in-memory one — normal for any crash, not a bug), correctly restored the player to their real in-game location withlook/scoreboth correct.clone/user/user.lpc'snet_dead()/reconnect()pair (destructs only the old *login* link object, rememberslink_ob/room vianosave/tempvars,reconnect()is actually wired up and called bylogind.lpc) is a sound, already- defensive implementation — this lib does not exhibit the AGENTS.md §7.20 void-parking class or the §7.21 stranded-mid-wizard class: the pre-registration character-creation wizard runs entirely on theLOGIN_OBconnection object itself (not a separate persistent player body), and that object's ownnet_dead()just destructs it after a 1-second grace timer (clone/user/login.lpc) — an unclean disconnect mid-creation simply abandons that attempt cleanly, with no permanent stranding, confirmed by reconnecting after several such abandoned attempts and always landing at a fresh您的英文名字:prompt. - Clean
quit+ real wall-clock-gap reconnect: state (location, stats, inventory) persisted correctly across a genuinequitand a later fresh login, for bothqinxiaandlinxue.debug.logstayed free oferror:/Too deep recursion/fatal lines after everyquitperformed in this pass. - Safe sparring (
fight): confirmed working exactly as documented (seeqinxia's playthrough above). - Organic skill learning: confirmed working (
xue dizi force,chashowed the new skill). - Sect-join code path: confirmed reachable and functioning as designed (
bai lingxiao, correctly declined per generation rules). - Admin account (
fluffos/Mud@2026, seeded in an earlier pass): re-verified end to end this pass — login,look,update /d/xiakedao/npc/longx(recompiled the fix live), cleanquit, all correct;(admin)privilege intact.
Explicitly NOT verified live (honest gaps, not silently skipped)
- Shop
list/buyat an actual merchant: not completed. This lib's real starting zone (d/xiakedao/, the island) is a large, fully self-contained ~100-room tutorial/quest area with no shop anywhere inside it (confirmed by grep — noinherit ... SHOPin anyd/xiakedao/*.lpc); reaching a real shop requires leaving the island for the mainland, which (per this lib's own design) generally happens via a sect's own recruitment escort (e.g. following 凌逍 to actual 华山) rather than a direct exit — not completed within this pass's time budget. Did verify a related, simpler item-economy mechanic instead:ask han about 武器(黄衣大汉, an island NPC) hands out a free weapon, confirmed working. - Real (lethal) combat and death/respawn: not attempted. Only the safe
fightsparring form was exercised live;kill/hitand the death → 鬼门关 → 武庙 revival flow described indoc/help/newbie.dec99were not tested, given the time already spent on the crash investigation/fix/re-verification above. - Full sect enrollment (an actual
baiacceptance, not just the correctly-declined attempt at 凌逍): would require reaching one of the actual sect masters (岳不群/宁中则 for 华山, perdoc/help/intro) off-island — not attempted within this pass's budget. The mechanism itself (feature/apprentice.lpc'screate_family/recognize_apprentice/bai.lpc) was verified structurally and via the one live, correctly-declined attempt.
WASM 修复摘要(迁移自 meta.json 的 group_note)
侠客行 XKX 基础版。
§7.86 跨库扫描修复(留言板 post 崩溃)
BULLETIN_BOARDinherit+ 多余replace_program()致命形状(AGENTS.md §7.86,post命令崩溃):全档案 40 处命中,已删除多余的replace_program(...)调用(保留inherit),逐文件保留原有行尾格式(CRLF/LF 按文件原样)。本次为跨库 §7.86 扫描修复(触发原因:该 bug 已在 6+ 个互不相关的血统家族独立确认,属于近乎普遍的拷贝粘贴模式),仅做编译检查(驱动干净启动、端口正常监听),未做完整 §10.7 深度游玩测试。
§7.112 跨库扫描修复(无常 NPC 重连触发重复轮回链)
d/death/npc/{wgargoyle,wgargoyle1,bgargoyle}.lpc的init()无条件调度death_stagecall_out 链(AGENTS.md §7.112):这三个文件原本都没有任何去重判定——enable_commands()会在同房间内向所有对象重播init(),玩家哪怕只是断线重连一次,也会在原有的death_stage/最终判官对话链之上再叠一条新链,导致重复的判官对白、重复扣血/转生等竞态错误。已仿照同族已修复库(dtsl/dtsl2/dtslmud/jym/xuanjianlu)的做法,给每个init()加上按受害者存的set_temp("death_stage_active", 1)/query_temp(...)门槛判定,并在death_stage()的每一个退出点(角色离场、黑无常"阳人退回"分支、链条走完转生)里delete_temp(...)清除标记。三个文件形状与xkm版本几乎一致(均无final_death_stage分支)。已用独立驱动干净编译+启动验证,未发现残留 save 数据变化。
§7.100 房间基类 replace_program() 扫尾修复(2026-08-19)
ROOM 宏(同一份 include/globals.h,重复定义两次,后一个
/inherit/room/room 生效,宏名不受影响)在本档案 2,210 处房间文件
的 create() 里紧跟 inherit ROOM; 之后又多余调用了一次
replace_program(ROOM);——AGENTS.md §7.100 记录的同一个休眠 bug,
和姊妹档案 bmxkx2001(同一份 clone/misc/roommaker.lpc 工具、
同一批受影响房间)逐字节一致。用 fix_710_room.py 扫过 work/,
删除 2,209 处标准形状;clone/misc/roommaker.lpc 剩下 1 处字符串
拼接变体,手工改成 str += "\n\tsetup();\n}\n";。修复后 work/
下 0 处存活残留,278 处转档之前已注释掉的 // 行原样保留,
work/data/ 下没有真实 .lpc 源码命中。git diff --stat 显示
2209 个文件净删 2210 行,与脚本自报数字 + 1 处手工编辑吻合。
驱动干净启动(零新增编译错误、端口正常监听、debug.log 无任何
"cannot replace"/"cannot bind"行),巫师账号 fluffos/Mud@2026
登录后确认"目前权限:(admin)",look/goto 走读了 2 个曾经命中
过这个 bug 的房间(d/zhongnan/zoudao4.lpc、
kungfu/class/baituo/btyard.lpc,与 bmxkx2001 完全相同)均正
常,quit 干净退出。登录存档的时间戳增量已用 git checkout HEAD
-- 撤销,未落入提交。
§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): 4 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.
Deep functional test (round two), standing cross-cutting pattern audit (this session)
The earlier "深度功能测试 / Deep functional test (round two)" section above
(commit 148389b7af4) already did the full hands-on playthrough this
project's §10.7 methodology calls for (registration, organic skill
learning, safe sparring, sect-contact attempt, quit/reconnect,
disconnect handling, admin re-verification) and found/fixed the
longx.lpc double-call_out driver segfault. This pass did not
repeat that playthrough (redoing it would just duplicate already-solid,
still-current evidence); instead it audited this lib specifically
against every standing cross-cutting bug-pattern class documented in
AGENTS.md as of 2026-08-27 (§7.121/§7.122/§7.123/§7.124/§7.126/§7.129
through §7.135, §8.3a, plus the wimpy/env-wimpy dbase-key check),
since most of those entries post-date the July pass and had never been
checked against this specific lib. Native driver
(~/src/fluffos/build-debug/src/driver config.fluffos),
scripts/mudclient.py, admin account fluffos/Mud@2026.
Bug found and fixed: §8.3a private-demotion, THREE more instances in this lib (new confirmed instances of an existing AGENTS.md class)
This exact "ES II → XKX" lineage is independently on record in AGENTS.md
§8.3a as having this bug in xuanjianlu, zjdyaryl, revive and
(via a from-scratch discovery) demonangel — always in the same two
file/function shapes (feature/action.lpc's eval_function(),
std|inherit/item/combined.lpc's destruct_me()). xkx2001 turned out
to carry the identical, previously-unchecked bug in all three places it
can occur in this archive:
1. feature/action.lpc:70 — private void eval_function(function
fun) { evaluate(fun); }, the sole callback target of
start_call_out() (the shared "temporary condition recovery
call_out" primitive; grep found 66 files calling
start_call_out(), almost entirely kungfu/skill/*.lpc buff/DoT/
timed-status effects). feature/action.lpc is inherited into every
character via inherit/char/char.lpc's inherit F_ACTION;, so
eval_function becomes DECL_HIDDEN once inherited and every
start_call_out()-scheduled callback across all 66 files would
silently fail to fire (apply() with insufficient permission ...
needs: private, has: hidden) the moment it actually fired, well
after the triggering action itself looked completely normal —
exactly the failure mode AGENTS.md's write-up describes.
2. inherit/item/combined.lpc:19 — private void destruct_me() {
destruct(this_object()); }, the sole callback target of
set_amount()'s v==0 branch (::move(VOID_OB);
call_out("destruct_me", 1);). inherit/item/money.lpc (the base of
every gold/silver/coin object in the game) inherits COMBINED_ITEM
and does not define its own destruct_me, so this is the exact
"money spent to exactly 0 orphans a permanent VOID_OB clone" shape
called out for xuanjianlu — every time any player's coin/silver/
gold stack hits exactly 0 (an everyday shopping event), the spent
currency object would move to VOID_OB and then leak there forever
instead of actually destructing.
3. d/xueshan/inherit/liquid_content.lpc:26 — byte-identical
private void destruct_me() / set_amount() shape, inherited by
d/xueshan/obj/suyou.lpc (a liquid-container item) with no local
override — same leak class, smaller blast radius (one item file).
Fix: dropped private from all three (kept every other qualifier;
none of the three needed nomask since nothing else in the codebase
overrides them). Mechanical, one-line-per-file, behavior-preserving —
matches the standard remedy already established for this exact bug
class.
Live-verified, both distinct call_out target shapes, via the seeded admin account:
eval_function:eval this_player()->start_call_out((: debug_message("XKX2001_EVAL_FUNCTION_TEST_OK\n") :), 1)returnedResult = 1(scheduled), and the marker stringXKX2001_EVAL_FUNCTION_TEST_OKappeared in the driver's own stdout ~1s later with zeroinsufficient permissionlines indebug.log— confirms the previously-private, now-publiceval_functionis reachable bycall_out()once inherited into a live player body.destruct_me: cloned/clone/money/coin, moved it onto the admin body, confirmed present viai;present("coin_money", this_player())->set_amount(0)immediately moved it out of inventory;sizeof(all_inventory(load_object("/clone/misc/void")))read1right after (the orphaned coin sitting inVOID_OBawaiting itscall_out) and0after a further 3-second real wait (the fixeddestruct_me()fired and actually destructed it) —debug.logstayed free ofinsufficient permissionthroughout. Did not separately re-run this exact repro againstliquid_content.lpc(identical code shape, already proven correct viacombined.lpc); confirmed only that the file still compiles clean post-fix.
Sweep methodology for future sibling checks: grepped every
private\s+(?:nomask\s+)?(?:varargs\s+)?TYPE NAME( declaration in the
whole archive against call_out("NAME"/add_action("NAME" occurring
in the *same* file, then hand-filtered the ~50 raw hits down to
real instances by checking whether that file is ever inherited by a
*different* file (the DECL_HIDDEN-on-inherit bug only bites across an
inheritance boundary — a private function whose only caller is the
same standalone leaf/clone object that declares it, e.g. dozens of
go_home/remove_effect/destruct_me-in-their-own-weapon-file
patterns found in this same grep, is unaffected and was correctly left
alone). feature/command.lpc's command_hook itself was independently
re-checked and is not private here (nomask int command_hook) —
already correct, unlike the sibling libs where that specific function
needed the fix.
Everything else audited this pass: confirmed clean, no fix needed
- §7.122 (autoload/marker-item duplication on disconnect/reconnect): this lib's own mechanism (
feature/autoload.lpc'ssave_autoload()/restore_autoload(), ES2-lineage naming, not the TMI-2compute_autoload_array/load_autoload_objnaming that the original AGENTS.md §7.122 bug was found under, despitexkx2001being listed as anes1descendant worth checking) is not vulnerable to the TMI-2-style double-storage bug:feature/save.lpc'ssave()is a baresave_object(file)(the native efun, which never serializes inventory sub-objects at all — no redundant "baked into ordinary inventory data" copy exists for this lib the way it does in the TMI-2 lineage's own custom recursive save), and theautoloadvariable is synchronously zeroed (clean_up_autoload()) immediately after every single consumption, both on the save side (clone/user/user.lpc'ssave()) and the restore side (restore_autoload()'s own tail call) — so a stray re-entrantsetup()call (checked every call site:logind.lpc'senter_world(), and the admin-onlypromote/award/cloneusrcommands) either operates on a brand-new empty-inventory clone or findsautoloadalready back to0(pointerp(0)false, immediate return) and is a safe no-op either way.reconnect()(the real net-dead recovery path) never callssetup()at all. Traced every->restore()call site in the archive; none of them callrestore()on an already-live/online object (all target either a freshnew(...)dummy/LOGIN_OB clone or bail out early via apresent()/find_player()online-check first). wimpy/env-wimpydbase-key mismatch: re-verified clean —cmds/usr/wimpy.lpc(the player command) andinherit/char/char.lpcheart_beat()(the real consumer) both consistently use"env/wimpy". Noted, but NOT fixed as it's a narrow, single-NPC content quirk rather than the systemic pattern being checked for: two NPC files (d/city/npc/maskman.lpc,d/huashan/npc/maskman.lpc) callset("wimpy", 30)(wrong key, dead write, that one NPC just never auto-flees) — cosmetic/content-only, in scope boundary territory (could be an intentional "this masked-man boss fights to the death" design choice), left untouched per this project's programming-bugs only mandate.- §7.121 (float arithmetic in declared-
intfunction): no int-declared field/function anywhere in the money/finance path (feature/finance.lpc'scan_afford()/pay_money()) uses float math at all — pure integer arithmetic throughout. The only stray= 0.N;float-literal assignments found archive-wide (d/taohua/obj/taomu(-)?jian.lpc'sgain3) are to afloat-declared variable, not anintone — not an instance of this class (and any balance question about the clamp value itself is a content judgment, out of scope). - §7.123 (bare file-scope
IDENT = (...)statement): grepped for the shape archive-wide; every hit found is inside a function body (create()/reset()/etc.), never a genuine top-level/file-scope statement. - §7.124 (fraction-vs-percentage threshold mismatch):
env/wimpyand every other percentage-shaped threshold field checked use consistent 0-100 integer literals throughout; no0.NNfraction literal assigned to a percentage-typed field found. - §7.126 (stale pre-
.lpc.cextension in.osave data): this lib has no macro-placeholder/coordinate-AREA door-storage mechanism at all (file_path()-style runtime path reconstruction from save data doesn't exist in this archive) — not applicable. - §7.130 (unconditional liveness-check call after non-interactivity already detected):
inherit/char/char.lpc'sheart_beat()has an explicitif (!interactive(this_object())) return;guard strictly BEFORE its ownquery_idle()call a few lines later — correctly ordered, not the buggy shape. - §7.131 (
find_living/find_playerneedingset_living_nameregistration):feature/command.lpc(inherited into every character) callsset_living_name(query("id") || query("name"))for every character — registration is real and universal; not applicable. - §7.132 (
map()-over-mapping wrong-argument binding): nomap(mapping, function)call site exists anywhere in the archive (only unrelatedquery_skill_map()-named accessors and a coordinate-gridgenmap.lpc) — not applicable. - §7.133 (disconnect-notification apply never defined): this driver calls
net_dead()on the player object directly (not a master-levelremove_interactive()apply), andclone/user/user.lpcdefines a real, workingnet_dead()/reconnect()pair (already confirmed working in the July pass's "Also verified working" section above) — not applicable. - §7.134/§7.135 (uninitialized-array field / accessor missing a lazy-init guard sibling has): the concrete instances of this general shape in this archive (
feature/skill.lpc's 4 uninitialized-mapping accessors) were already found and fixed in the earlier §7.30 sweep (see above, commitfa23db1fa2e); no further un-guarded sibling-accessor gaps found on a fresh look at that file or its neighbors.
Live verification summary
Clean boot (native driver, zero compile errors/warnings-as-errors,
zero debug.log fatal/segfault lines across the whole session).
Logged in as fluffos/Mud@2026 (目前权限:(admin) confirmed),
landed correctly at the 沙滩 starting room. Ran the two live repros
above (eval_function via start_call_out, destruct_me via a
coin spent to 0) with debug.log clean throughout, then quit
cleanly. No test-character saves were created this pass (all testing
used the existing seeded admin account plus throwaway new()-cloned
test objects that self-destructed or were never saved); no additional
save-file cleanup needed beyond reverting the admin account's own
login-timestamp/hunger-tick save drift from this session's testing
(git checkout HEAD -- on work/data/{login,user}/f/fluffos.o) and
removing the disposable work/tmp/tmp_eval.lpc eval scratch file.
AGENTS.md §7.19 fix: enable_player() reentrancy from init()
feature/command.lpc's enable_player() (wrapper around
enable_commands()) was reachable from an NPC's init(): the shared
inherit/char/char.lpc setup() (called from every character's
create()) itself calls enable_player(), and d/zhongnan/npc/killer.lpc
redundantly calls setup() again from inside its own init() (on top
of the setup() its create() already made) -- same shape as the
originally-documented mhxy zhangmen.lpc case. enable_commands() is
only safe to call from create(): calling it again on an object
already living() makes the driver re-invoke that same object's
init() as a side effect, which recurses back into enable_player()
on the same call stack until "Too deep recursion" aborts the boot on
a room's first-ever visit. Fixed with a true reentrancy flag
(in_enable_player_now), NOT a living() guard (which would break
legitimate re-enables from revive() in feature/damage.lpc and
wakeup()/wakeup2() in cmds/std/sleep.lpc, both confirmed to
re-invoke enable_player() on this lib while the object is still
living()). Verified via lpcc --batch single-file compile check
(PASS). Part of the corpus-wide §7.19 sweep (Batch E).
深度功能测试(2026-09-04,round three,shop + 拜师)
新角度:扬州醉仙楼购物 + 丐帮左全拜师。round two 诚实记录「Shop list/buy
at an actual merchant: not completed」(侠客岛起始区没有商店)以及「Full
sect enrollment 未完成」(只测了凌逍按辈分拒收)。巫师 goto 可以直接
离开岛屿。
实测过程
管理员 fluffos / Mud@2026 / 浮浮。第一输入是 Do you want to use
BIG5 code?(y/n),回 n 用 GB。落地侠客岛沙滩 /d/xiakedao/shatan。
score 「【天帝】普通百姓」。log_error() 已有 arning: 闸门,本轮
屏幕上没有编译警告倾泻。fluffos 的 wiz_sites 是 .*。
goto /d/city/zuixianlou,list 烤鸡腿四十文铜板 / 牛皮酒袋五十文 /
包子三十文。F_DEALER 里丐帮穷叫化拒绝购买整段是注释掉的,仍按家族惯例
先买再拜。clone /clone/money/gold 后 buy jitui 成功(「你从店小二那
里买下了一根烤鸡腿」)。i 剩六十文铜钱 + 九十九两白银(10000−40 =
9960),黄金条目消失,找零数学正确。
goto /d/gaibang/inhole,左全源码是 kungfu/class/gaibang/zuo-quan.lpc,
apprentice zuo 一次成功:左全收徒,score 「丐帮一袋弟子」、师父左全、
恭喜成为第二十代弟子。cmds/usr/save.lpc 真正调用两个 save()。
user.o 立刻带上 family_name":"丐帮" / master_name":"左全" /
generation":20。断线后再连(「重新连线完毕」),称谓/师傅/银子/鸡腿
还在。左全只收男性。
本轮没有新的 programming bug。live debug.log 是
libs/xkx2001/log/debug.log(Boot Time Fri Sep 4 02:46:07 2026),无
error: / Too deep recursion。管理员存档未提交。