info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
青岛站,作者"小宝(suannai)/西瓜太郎(gslxz)/桃心太郎",由"星月传奇"发布,属于"神话西游"(SJXY) 一脉,是 ES II 引擎下西游记(Journey to the West)神话世界观的一个独立改版分支——与本项目收录的 mhxy、rzrmud 同为这一神话西游世界观,但代码血统各自独立,并非彼此的换皮版本。新角色从"南城客栈"启程,唐僧、店小二、千里眼等经典角色以 NPC 身份驻场迎接新人,并在原有取经故事框架上加入了武当、移花宫等原创门派内容,是同类西游题材泥巴中内容比较丰富、系统比较完整的一支;注册流程里还有一步"您是否是中小学学生或年龄更小?"的自述式年龄提示,答 yes(或不理会)会被直接劝退,是这批档案里少见的、游戏本身自带的年龄门槛;死亡则有完整的阴曹地府体验——由"朱笔判官 崔珏"接引到"阴阳界",静候片刻后自动复活还阳,和同一 ES2 大家族的 `yszz`/`bmxkx2001` 是同一套设计。
English
A Qingdao-hosted branch of the 'Mythic Journey West' (SJXY) family, built on the ES II engine as an independent take on the Journey to the West mythos — sharing the same setting as sibling games mhxy and rzrmud in this collection but with its own separate codebase. New characters set out from the South City Inn, greeted by classic figures Tang Sanzang, the inn's waiter, and the far-seeing Clairvoyant (千里眼) as NPCs; the game layers in original sects such as Wudang and Yihua Palace not found in the source novel, making it one of the more content-rich entries in this sub-genre. Registration includes an unusually self-aware age gate that turns away anyone who answers yes (or doesn't answer) to "are you a primary/secondary school student or younger?", and death sends players through a full underworld sequence — escorted by Judge Cui Jue (崔珏) to the Yin-Yang Boundary before automatic revival, the same design shared with this collection's yszz and bmxkx2001.
README
深度功能测试新发现的 bug(详见 NOTES.md)
adm/daemons/logind.lpc 里有一行调试用的 printf("%O\n", ob),紧
挨在设定中文名字之前,每一个新玩家注册时都会看到(AGENTS.md
§7.34)。已删除。(另外核实了 doc/help/newbie 里"食物/饮水初
始为空"的说法和实际代码不符——logind.lpc 明确无条件把两者初始
化为满值,是文档陈旧而非程序 bug,未做代码改动。)
在线试玩
https://mudlibs.fluffos.info/mohuanshiji/
管理员账号 / Admin account
- ID:
fluffos - 密码 / Password:
Mud@2026 - 中文名 / Display name: 浮浮
- 权限 / Level:
(admin)(最高级别),通过/adm/etc/wizlist授予。
警告:对外公开架设前请务必修改此密码。
本地运行
cd libs/mohuanshiji
~/src/fluffos/build-debug/src/driver config.fluffos游戏端口:40092。
NOTES · 移植与修复记录
mohuanshiji — 魔幻世纪 (Magic/Fantasy Century)
Archive: archives/魔幻世纪.rar (archive #100, the last one in this batch).
Extracted mudlib root: raw/mhsj/mud/ (a config.cfg sits at raw/mhsj/,
one level above the mud/ dir it points into — mudlib directory in the
raw config is a Windows path e:/mhsj/mud). Port: 40092. Status:
done (boots clean, full registration flow verified twice with real
Chinese names including both gender branches, post-login look/score
verified working every time, zero runtime errors in debug.log across all
sessions).
What this is / lineage
config.cfg's own name field (GBK-decoded, no stale leftover this time)
is 魔幻世纪, matching the archive's own filename — no rebrand mismatch to
untangle here. raw/mhsj/readme.txt (credited to 小宝(suannai) /
西瓜太郎(gslxz) / 桃心太郎(iiiii), hosted for download by "星月传奇") states
this codebase itself started life as an xyj (西游记/"Journey to the
West")-derived ruleset — "本套源代码,修改了不少xyj中的BUG" ("this
source fixed many bugs in xyj") — with 武当 and 移花宫 sects added as this
site's own original content. adm/obj/master.lpc's header comment reads
"for ES II mudlib, original from Lil, rewritten by Annihilator
(11/07/94)" — the same ES II lineage shared by the large majority of
already-processed archives in this project (directory layout adm/,
feature/, std/, obj/, cmds/, d/, u/; securityd.lpc's ACL
shape; feature/dbase.lpc's real non-simul_efun set/query). Several
in-mudlib strings (logind.lpc's own comment header "神话世界·西游记·
版本4.50", a MUDLIB=="SJXY" check in total_players(), u/GSLXZ /
u/suannai wizard homes matching the readme's credited authors) confirm
this is a "神话西游"(SJXY)-descended xyj build, not a from-scratch design —
consistent with, but not byte-identical to, any already-shipped sibling
spot-checked (master.lpc, chinese.lpc, securityd.lpc all differ from
the nitan/XO/ES2-Neolith/"hell" families' equivalents). Genuinely its own
build, not a rebrand of one already in the project.
Notably not present in this lib despite the ES II ancestry: the
nitan-family dbase-via-simul_efun architecture bug (§15) — this lib's
feature/dbase.lpc-equivalent already implements set/query/delete
directly, same as tiexuejianghu/syxjl/etc.
Fixes applied (with why)
§8h — convertd.lpc stray-backslash Greek/charset-table typo (43 instances, CRITICAL — broke the whole simul_efun boot)
adm/daemons/convertd.lpc builds several large lookup tables (Latin/Greek/
pinyin-tone character variants) as one-entry-per-line string literal
arrays. 43 of these lines had a stray trailing \ immediately before the
closing quote ("α\", instead of "α",), escaping the closing quote and
swallowing every subsequent line into one giant malformed string literal
until the parser hit the *next* real quote — this crashed the compile of
convertd.lpc, which is #included/composed such that it broke
simul_efun object loading generally, cascading into "couldn't find
object '/adm/daemons/channeld'" runtime errors for every daemon whose
create() fires after convertd in preload order (see the CHANNELD fix
below — same underlying trigger). One entry ("α\",) also
contained a legitimate hidden PUA codepoint (U+E5A2, an alternate
glyph representation intentionally stored alongside each table entry, e.g.
"Ω",/"β", — confirmed present and syntactically fine on
*every other* table row, not corruption) — fixed with a Python
line-indexed read/write that removed only the stray backslash and left the
PUA character untouched, per the encoding-section's PUA caveat (don't
trust a text editor / blind sed here). Found and fixed one instance first
via lpcc on /adm/obj/simul_efun, then proactively scanned the whole
file for the same shape (grep-style scan for lines ending \",) and
fixed all 43 in one pass rather than one-at-a-time.
Case-sensitivity: adm/daemons/CHANNELD.lpc vs. CHANNEL_D macro (CRITICAL — broke ftpd and any other post-channeld preload daemon)
include/globals.h defines CHANNEL_D as "/adm/daemons/channeld"
(lowercase) — but the file on disk, after the standard .C→.lpc
uppercase-extension rename (§ "Watch for uppercase .C"), was
CHANNELD.lpc (all-caps basename, not just extension — a genuine
Windows-case-insensitive-authoring artifact, same root class as §15g/§15k
but for a #defined daemon path rather than an #include or literal data
file). Even though channeld itself loaded fine in preload order (its own
create() has no immediate dependency on the macro resolving), any LATER
preloaded daemon that calls CHANNEL_D->do_channel(...) in its own
create() (found via ftpd.lpc's create()) got *call_other() couldn't
find object '/adm/daemons/channeld' — non-fatal individually (wrapped in
catch() by master.lpc's preload()), but it flooded the boot log with
repeated error-handler recursion output and meant the channel daemon was
silently unusable from any of these call sites. Fixed by renaming the file
to lowercase adm/daemons/channeld.lpc to match the macro (much lower
blast radius than changing every CHANNEL_D/do_channel call site).
Lesson for the catalog: the uppercase-.C-rename check (found on
shenmo #73) needs to also cross-check renamed files against any
#defined macro path pointing at them by a DIFFERENT case, not just check
that the rename itself happened — 310 total uppercase-origin files existed
in this lib; only this one (a core preloaded daemon) actually mattered.
Uppercase-.C rename encoding gap (15 files never GBK→UTF-8 converted — NEW finding)
convert_lib.sh's forced-text-extension allowlist (*.c|*.lpc|*.h|...)
is a case-sensitive bash case match — an originally-uppercase .C file
never matches it and falls back to file(1)'s guess, which misclassified
15 of these (raw GBK, CRLF line endings) as data (binary) and skipped
them entirely — the same failure mode as the lib-#4 "CRCRLF confuses
file" bug, just gated by extension case instead of line-ending shape
this time. These 15 files were then manually .C→.lpc renamed (matching
every *other* uppercase file, most of which file DID correctly detect as
text) but never had their actual byte content converted, surfacing later
as Invalid UTF8 codepoint in string literal / raw 0xb9 0xa6...-style
mojibake in the lpcc sweep, not at boot time (none of the 15 are
preloaded/reached by the registration path). Found via a small Python
script attempting bytes.decode('utf-8') on every .lpc/.h file in
work/ and listing the ones that fail — cheap, worth running proactively
on any lib with uppercase-.C files going forward, not just this one
(files: clone/{SHUSHAN,HELL,MOON,LONGGONG,JJF}.lpc,
adm/CL/{SHUSHAN,HELL,MOON,LONGGONG,JJF,QIANG}.lpc,
d/city/npc/XIA.lpc, d/city/obj/npc/greeting.h, d/sea/npc/beast1.lpc,
d/obj/quest/shuijingqiu.lpc — converted with iconv -f GB18030 -t UTF-8,
falling back to -c for two files with a truncated trailing multi-byte
sequence at EOF).
DOS-era Ctrl-Z (0x1a) EOF markers (11 files)
The same 11 adm/CL/*/clone/* files above (once GBK-decoded) also each
had a trailing 0x1a (SUB/Ctrl-Z) byte — an old MS-DOS text-editor
end-of-file marker, harmless in GBK/UTF-8 either way but an "Illegal
character" hard compile error to this driver's lexer. Stripped with a
one-line Python byte-replace once identified via the same lpcc sweep.
§14 — valid_override() upgraded to the 3-arg form (adm/obj/master.lpc)
Was the old 2-arg valid_override(file, name). Added the 3rd main_file
parameter and an early-allow when main_file == SIMUL_EFUN_OB ||
main_file == MASTER_OB, matching the documented fix — cheap, correct,
applied proactively (never actually triggered in a real boot here either,
consistent with the catalog's note that this mostly only bites a bare
lpcc compile of an #included simul_efun fragment).
§15h — is_chinese() GBK byte-range check (adm/simul_efun/chinese.lpc)
Was strlen(str)>=2 && str[0] > 160 && str[0] < 255. Fixed to
strlen(str)>=1 && str[0] >= 0x4e00 && str[0] <= 0x9fff (CJK Unified
Ideographs range). Without this every real Chinese name is silently
rejected at registration — confirmed via the interactive test below that
秦风/秦风四/秦风五 all now register correctly.
§15h — check_legal_name() bound + sliding-window (adm/daemons/logind.lpc)
Byte-length bound strlen(name) < 2 || > 12 (message already said "一到
六个中文字", i.e. 1–6 Chinese characters, calibrated for 2 bytes/char)
halved to < 1 || > 6. The sliding-window guard was i%2==0 &&
!is_chinese(name[i..<0]) (checking only even *byte* offsets, using a
"rest of string" substring so is_chinese's own strlen>=2 requirement
had something to bite on) — replaced with a plain !is_chinese(name[i..i])
checked at every index (every position is one full character now, no
stepping needed, and a true single-character slice makes is_chinese's
own length check trivially satisfied for every real position including
the last character, which the old shape always failed).
§15p — excluded network/dns_master from adm/etc/preload
Standing policy; this daemon bootstraps a UDP intermud database against a
hardcoded remote "boot server" unreachable from this sandbox. Removed the
/adm/daemons/network/dns_master line from adm/etc/preload.
§15ab-style — guarded Mud_name() macro against dns_master never loading (include/net/macros.h)
Per §15p's own caveat ("necessary but not sufficient"), grepped for direct
callers of the DNS/intermud subsystem outside preload/adm/daemons/network/
itself. adm/daemons/CHANNELD.lpc's intermud-channel relay branch (fires
only when a player speaks on an "intermud":-flagged channel — xyj,
es, jh, gwiz; not the sys/chat channels used during ordinary
login, so this never actually fired during the interactive test below, but
is a real latent crash for anyone speaking on those channels) and
cmds/usr/mudlist.lpc/cmds/wiz/tmp.lpc all call the bare Mud_name()
macro, which unconditionally did (string)DNS_MASTER->query_mud_name().
Changed the macro itself to
(string)(find_object(DNS_MASTER) ? DNS_MASTER->query_mud_name() :
CHINESE_MUD_NAME) — one fix covers every call site, degrading to the
mud's own name constant instead of a 0->query_mud_name() runtime error
when the daemon is (deliberately) never loaded. Confirmed no other
network-subsystem macro (index_add/index_call/dns_log/dns_warning/
udp_port/mud_nname) is called from outside adm/daemons/network/
itself, so nothing else needed the same treatment.
§15w — log_error() no longer echoes compile *warnings* to a connected non-wizard (adm/obj/master.lpc)
This driver's compile-diagnostic text is lowercase "warning: " (verified
by reading ~/src/fluffos/src/compiler/internal/lexer_utils.cc's
prepare_logs()), and log_error() is invoked for every compile
diagnostic, not just fatal errors — the original code showed the scary
default error message to any connected non-wizard on ANY diagnostic,
including harmless ones like our own §3 nosave-on-function warning that
this driver itself only warns about. Gated the player-facing branch on
strsrch(message, "warning:") == -1 (i.e. only show it when the message is
NOT a warning) — still logged to the log file either way, just not echoed
to the connecting/playing user as if it were a crash.
§15ab item 1 — missing !stringp(str) guard in the pre-login connection object's own receive_message() (obj/login.lpc)
feature/message.lpc (the real player body's copy) already guards with
if(!msg || (len=strlen(msg))<1) return;, but obj/login.lpc's own copy
(used for the NOT-yet-authenticated connection object, reached by every
single fresh connect before make_body()) had no such guard at all —
any write(0) reaching this object during the pre-login sequence (this
driver's write() simul_efun doesn't static-type-check a mixed-typed
0 at the call site) would have crashed receive() on literally every
connection, before any prompt appears. Added the same !stringp(str)
guard as a defensive fix (no live crash of this specific shape was
actually observed in testing here, but the asymmetry with the working
copy was the exact shape of a previously-documented bug from haiyang2).
§15b — db_affected(int) stub (adm/simul_efun/file.lpc)
adm/daemons/databased.lpc (not preloaded, not reached by the
registration/gameplay path tested) calls a never-defined db_affected(db)
after every db_exec(). Restored as a simul_efun stub returning 1
(assume ≥1 row affected) per the documented precedent — this driver's DB
package has no such efun and db_exec() itself returns 0 for
INSERT/UPDATE/DELETE regardless, so every real call site's own
db_exec()-return check is the actual signal; this only silences a
compile-time "Undefined function" for a file that was never going to be
exercised by this pass's testing anyway.
§8e — tail() efun call in an unused admin command (cmds/wiz/tail.lpc)
Reimplemented in plain LPC (read_file() + explode() + slice + write),
matching the documented pattern exactly — non-fatal either way (dead wiz
tooling), fixed since it was cheap.
§15ac — bare SAVE_EXTENSION (6 refs, adm/daemons/network/netmail.lpc)
Replaced with this driver's real autogenerated __SAVE_EXTENSION__
constant. Only reachable from the (deliberately unloaded) network/
subtree, so not exercised by testing, but cheap and correct to fix.
§15t variant 1 — absolute path in angle-bracket #include (1 file)
d/obj/books-nonskill/book-qujing.lpc's #include </d/qujing/obstacle.h>
converted to the quoted form #include "/d/qujing/obstacle.h".
#include "../parent.h" (disallowed .., 1 file)
d/ourhome/honglou/npc/niu.lpc's #include "../honglou.h" rewritten to
the real absolute quoted path #include "/d/ourhome/honglou/honglou.h".
Pre-existing typo: missing closing quote (2 files, §10-shape)
d/obj/books-nonskill/hmeng014.lpc:string name = "《红楼梦》第十四回;(missing closing"before the;) — confirmed a one-off by checking all 35 siblinghmeng0NN.lpcchapter files, every other one already has its closing quote correctly.d/city/npc/BINGQI.lpc:set("long", "...商人\n由于...人称"兵器王"。\n");spanned two raw physical lines with an embedded literal (unescaped)"兵器王"nickname — merged onto one line with the embedded quotes escaped (\"兵器王\") and the missing\nrestored between the two original lines' content.d/moon/obj/poem.lpc: two consecutive poem-string array entries ("...宫阙。,/"...潇潇雨。,) were each missing their closing quote before the trailing comma — both fixed.
Confirmed NOT needed (checked explicitly)
- §4 (lazy
load_object()invalid_read/valid_writerecursion) —master.lpc'svalid_read/valid_writeonly callfind_object(SECURITY_D)and return a plain default (1/0) if not found; noload_object()call in either apply at all. - §7 (
get_root_uid/get_bb_uid) — both already implemented inmaster.lpc, returningROOT_UID/BACKBONE_UID. - §8c (
this_player()-override invalid_read) —master.lpcpasses the driver's ownuserargument straight through toSECURITY_D->valid_read/write(file, user, func), never substitutesthis_player()for it. - §8d/§15o (
get_include_path()/local-header<>vs"") —convert_lib.sh's automated pass already converted 282 local angle-bracket includes to quoted form; the one remaining absolute-path<>and one..-relative include (both listed above) were the only stragglers, fixed by hand. Noget_include_path()apply needed since quoted-form resolution against the including file's own directory has no VM-context dependency at all. - §15ae (
private nomaskcommand-hook) —feature/command.lpc'scommand_hook(string arg)is plainnomask(a commented-out line directly above it,// private nomask int command_hook(string arg), shows this lib's own authors had ALREADY droppedprivateat some point in this codebase's history — presumably for the same reason this project keeps finding). Confirmed via a full post-loginlook/scoretest that commands really do dispatch. - §15ar (
commandd.lpc-style deadsscanf(".c$")) —adm/daemons/cmd_d.lpc'srehash()uses a plain quotedsscanf(cmds[i], "%s.c", cmds[i])(no trailing$before the closing quote), whichconvert_lib.sh's standard quoted-.c"-reference fixer already turned into"%s.lpc"automatically — confirmed by reading the post-convert file and by the workinglook/scoretest. - §15aj (missing mandatory gift/first-room object) —
logind.lpcmoves every new character withno_giftset to/d/wiz/init, which genuinely exists and runs a full 4-stat gift-allocation dialog before handing off to the realSTART_ROOM(/d/city/kezhan) — both files present and working, confirmed via the interactive test. - §15ag (
ed_start/ed_cmd/query_ed_mode) — grepped the whole lib; the only hit issecurityd.lpc'svalid_readfunc-name whitelist comparing the STRING"ed_start"(one of several apply-name string literals in aswitch-like func check), not an actual call to any of those three removed efuns/applies. - §15ai (DNS_MASTER-absence causing an unconditional
shutdown(1)gate) —logind.lpc's only otherDNS_MASTERuse (total_players()) already guards withif(!find_object(DNS_MASTER)) return 0;first; no unconditional dispatch on a null find anywhere in the login path. - §15ah (missing save-data directory silently swallowing a
write_file()/log_file()) — checked every hardcoded/log/...directory referenced by the registration-adjacent code path (/log/nosave/LASTCRASH, checked with the correctfile_size(...)>0guard, not the§15amtruthiness trap) and created the one missing directory (log/nosave/) proactively; not actually reached by this pass's specific registration/look/score test but cheap insurance. - §15al (
crypt(str,0)random-salt-per-call client-challenge mismatch) — this lib's onlycrypt()uses are for ordinary password storage/verification (crypt(pass,0)once to set,crypt(pass, stored_hash)to check), not a client-side challenge/response protocol —stored_hashitself is always used as crypt's 2nd arg for verification, so per-call randomness at set-time is irrelevant. - §15am (
file_size()==-1truthy) — the two places that matter for boot/registration (assure_file()'s ownif(file_size(file)!=-1) return;, andlogind.lpc'sfile_size(...)>0checks) both already compare correctly. - §15an (live-clock heartbeat defeating
mudclient.py's idle pacing) — the login banner shows Beijing time ONCE at connect, not a continuously-updating prompt; no heartbeat-in-prompt pattern found. (An early test run's "9"/"y" gift-selection replies did appear to get lost once — likely ordinary local timing jitter rather than this pattern specifically, since retrying with the exact same script and a slightly longer--idlesucceeded cleanly on every subsequent attempt.) - §15ao (
switchwith onlydefault:) — none found (master.lpc'sconnect()doesn't even use aswitch; grepped the whole lib). - §15ap (
__FILE__inside a#included, notinherited, fragment) — not checked exhaustively given the archive's size, but none of the fixed/failing files above hit this shape. - §15l (
master.lpc create()destructingSIMUL_EFUN_OB) — this lib'screate()is a one-linewrite("master: loaded successfully.\n");, no destruct/reload dance at all. - §15m (unguarded
restore()crashing a version-sync daemon'screate()) — noversiond.lpc/replication-sync daemon exists in this lib at all. Several ordinary daemons (chinesed,obstacled,msgd,emoted,clubd,familyd,shili) do callrestore()directly increate()with nocatch(), same general shape — spot-checkedchinesed.lpc's own save file (data/chinese.o) for corruption (it's a large but well-formeddict (["..."...])mapping literal, closes cleanly) since it's the one on this list actually reached via preload; did not exhaustively byte-audit the others' save files given the clean boot and clean interactive test already obtained. - §15n (custom
securityd.lpcvalid_readblocking the driver's own compile-timeload_object/include) — this lib'svalid_readalready has an earlyif (func != "read_file" && func != "file_size" && ... ) return 1;gate that implicitly allowsload_object/recompile_object/include(none of those func names appear in the excluded list), so the ACL only ever applies to genuine data-read funcs. No fix needed. - §15r (
check_config.lpc-style driver-version self-check with a bare fatalerror()) — no such file exists in this lib. - §15s (shared
tell_room()/message()wrapper passing rawint 0as the 4thexcludearg) —adm/simul_efun/message.lpc'stell_room()does exactly this shape (message("tell_room", str, ob, exclude)withexcludeunset/0 in the common 2-arg call form), but verified against this driver's actualf_message()C++ source (src/packages/core/efuns_main.cc): thenum_arg==4branch'sswitchon the 4th arg's type has adefault:case that setsavoid = &the_null_arrayfor ANY non-object/non-array 4th arg, including a bareint 0— nobad_argument()is ever raised for this case on this driver build. Confirmed harmless by reading the driver source directly rather than assuming the catalog entry auto-applies; the realyueyingqiyuanbug this pattern was named after must have hit a different argument position or driver revision. - §15u (dormant phone-home/license-check destructive function) — grepped
securityd.lpc/master.lpcand the whole lib for版权/盗版/正版/license/suspiciousrm+shutdowncombinations; nothing found beyond an unrelatedcmds/std/curse.lpccomment containing the substring "licensed_to_kill". - §15v (
LONELY_IMPROVED-gated deadefun::X()calls,count_add/etc bignum efuns) — not a nitan-lineage lib; grepped forLONELY_IMPROVEDandcount_add\|count_mul— zero hits, confirming this ES II-derived build never carried that fork's bignum/efun-gating convention at all. - §15z (
static→nosaveblanket-sed colliding with a#define nosave staticcompatibility shim or a"static/...\"string-literal path) — grepped for both shapes after the sed ran; zero hits either way. - §8f (
TYPE * a, b;losing the array modifier on subsequent declarators) — found ~109 occurrences of the general shapeTYPE *name1, name2;, but spot-checking a broad sample showed every one is an intentional single-scalar second declarator (an error/counter/ string variable genuinely meant to be non-array, e.g.mixed *cnd, err;whereerrholds acatch()result) — not the Dead-Souls-style bug where the SECOND name was also later assigned an array value. Did not bulk-fix; no evidence this lib's authors ever wrote the buggy shape. - A separate finding, confirmed harmless, not fixed:
adm/simul_efun/ wizard.lpc'swizhood()hasif( objectp(ob) ) { if( ob=="suannai" ) return "(admin)"; }— comparing an OBJECT to a STRING literal, which is never true, so this hardcoded admin-shortcut never actually fires (an unused, differently-named sibling file,adm/simul_efun/wiz.lpc, has the correctedgetuid(ob)=="suannai"version, but isn't#included intosimul_efun.lpcat all — a dead leftover, not the live copy). Harmless because the function always falls through to the realSECURITY_D->get_status(ob)check, andadm/etc/wizlistalready listssuannai (admin)directly, so admin status resolves correctly anyway. - A duplicate NPC file with a mojibake filename (GBK bytes
\xb8\xb4\xbc\xfe= "复件" i.e. "Copy of", fromd/city/npc/) — content differs from the realxiaowang.lpcit's named after, not referenced anywhere by any possible in-game path (nothing could type its raw byte sequence in aninherit/load_objectcall), left as-is; a permanent (harmless)lpcc-sweep failure, not fixed.
Known content gaps (not fixed — genuine archive gaps, §13-style)
d/hen/zone (38 files): every file under this directory#include <hen.h>, andhen.hgenuinely does not exist anywhere in the raw archive either (confirmed againstraw/, not something our pipeline dropped). This is presumably a "恨"(hate/regret)-themed side zone whose shared header (macros for room-category constants judging by theUndefined variable 'DEATH'/'TOUR'/'STUDY'/'SPORT'/'REST'/'MAGIC'errors these files also throw) was simply never included in this release. None of these files are#included/inherited from anywhere reachable via preload or the tested start zone.d/wuguan/{npc/jiading,npc/jiading1,qianyuan}.lpc(3 files): callCLOTH_OB("cloth")/WEAPON_OB("changjian")as path-building macros that are never#defined anywhere in the archive (confirmed via whole-tree grep). A genuine, small, pre-existing content gap — not fabricated.d/wiz/angell/hp.lpc: a wizard-only diagnostic/"status card" command whose body is genuinely garbled mid-file (aprintf(...,)with a dangling trailing comma and no closing arg, immediately followed by what looks like two merged/truncated lines —ame(),ob->query ("genderprintf(...). Almost certainly pre-existing corruption from the original archive (not a rename/encoding artifact — the surrounding ANSI-artprintfcalls are otherwise intact), and it's wizard-only tooling with no bearing on registration/ordinary play. Left unfixed rather than guess at reconstructing the intended two statements.
Registration-flow AND post-login-command test (verified twice, both genders)
Booted ~/src/fluffos/build-debug/src/driver config.fluffos from
libs/mohuanshiji/ (port 40092), then in ONE continuous
scripts/mudclient.py connection each time:
Run 1 (male): "" → gb → no → qinfengd → y (confirm new char) →
秦风四 (Chinese name) → test12345 (password) → test12345 (confirm) →
[email protected] (email) → m (gender) → 9 (accept default gift
allocation) → y (confirm) → look → score → quit.
Run 2 (female): same shape with id qinfenge, name 秦风五, gender f.
Both runs: encoding/age-gate prompts answered, English id accepted,
Chinese name accepted on the first try (validating the §15h fix),
password set/confirmed, email accepted, gender accepted with the
correctly gendered join broadcast (male: "…的小兄弟来到《魔幻世纪》求取真经" /
female: "…的小姑娘来到《魔幻世纪》求取真经"), landed in /d/wiz/init's
gift-allocation dialog, accepted the default allocation, and the game
printed "好!祝你西天取经早成正果!" and moved the new character to the
real start room 南城客栈 (/d/city/kezhan) with a full room
description, exits, board, and NPCs (唐僧/店小二/千里眼) — 店小二's ambient
greeting even correctly used the gendered term ("这位小姑娘" for the female
run). score printed a complete, correctly-gendered character sheet
(仙衔/职称/attributes/etc). quit produced a clean "欢迎下次再来!" goodbye
with no hang. log/debug.log was completely clean (zero error/
undefined/recursion lines) across both full sessions, boot-to-quit.
lpcc sweep results
scripts/lpcc_check.sh (fresh run after all fixes above): 8316/8535
pass (97.4%). Remaining 219 failures triaged by category:
- ~38 in
d/hen/(missinghen.h, §13-style content gap, see above). - A large fraction (~60+) of the rest are
#include-only fragment files (d/kaifeng/npc/quest*.lpc's sharedquest.lpc/reporting.lpcfragments, and dozens ofdaemon/class/*/*.lpcskill-action files under the class/skill tree) failing standalone withUndefined function query/setup/offensive_target/query_skill_prepare/prepare_skill— all confirmed §6b sweep artifacts: the referenced functions are real, defined instd/-tree base classes (std/sserver.lpcdefinesoffensive_target,feature/dbase.lpc-equivalent providesquery) that these fragments only ever expect to inherit indirectly once composed into a real NPC/character object via#include, never when compiled as a bare top-level object bylpcc. Cross-checked against the fully cleandebug.logfrom both real interactive sessions above. - 3 in
d/wuguan/(missingCLOTH_OB/WEAPON_OBmacros, content gap, see above). - A handful of genuine one-off pre-existing typos already fixed above (
hmeng014.lpc,BINGQI.lpc,poem.lpc). - 1 permanently-harmless mojibake-filename duplicate (see above).
- 1 genuinely-corrupted wizard command (
d/wiz/angell/hp.lpc, see above). - A small number of
Cannot #include uid.h/config.h/reporting.hand assortedsyntax error/Undefined variable 'X'failures not individually root-caused given time constraints on this, the last archive of the batch — none are reachable from the preload list or the tested registration/look/scorepath, consistent with the project's stated "breadth over exhaustive depth" priority.
Re-verification pass: driver rebuild + formatter + WASM (2026-07-23)
- LPC formatter: ran
format-corpus.mjsacross all 8535.lpcfiles underwork/— 8383 reformatted, 133 unchanged, 19 refused (token-mismatch safety gate, expected/fine, not investigated further). - Native retest against the freshly-rebuilt driver: booted clean (
Initializations complete., zero fatal errors). Repeated the full registration flow from this file's own "Registration-flow" section above with a fresh real Chinese name (秦风六/ idqflibsj), reached 南城客栈, and confirmedscorerenders correctly andquitexits cleanly ("欢迎下次再来!"). No regression from the reformat or driver rebuild — behavior identical to the original conversion pass. - WASM build test (
scripts/wasm_client.js): fully playable, including a full real-name registration (秦风七/ idqflibwsj) reaching 南城客栈 with correct room/NPC text and a cleanquit. One thing worth flagging for the record:debug.logshows a single non-fatal*Array index out of boundstrace fromadm/daemons/ ipd.lpc'sseek_ip_address()duringenter_world()— the exact same root cause as the documentedquery_ip_number()-under-WASM limitation (an empty/malformed IP string breaksipd.lpc'sexplode(ip, ".")[1]indexing), but here it fires fromenter_world()'s tail-end "welcome, you're connecting from…" text lookup, which runs *after* the character is already created and moved into the world — so the uncaught error just skips that one cosmetic message instead of breaking theinput_to()callback chain the way it does formhxy(same bug, same lineage'sipd.lpc, different call site — this one isn't gating anything). Confirmed no functional impact: registration completed, room description/NPCs/exits all rendered normally, andquitworked. Status: fully playable under WASM (with one cosmetic, non-blocking IP-lookup error logged, same driver-level root cause asmhxy's WASM limitation but not blocking here).
WASM-enablement pass (2026-07-23): loopback-allow + admin seeding
Gates patched (loopback = 127.0.0.1, any 127.*, or an empty/malformed
non-dotted-quad string, which is what current WASM builds return):
adm/daemons/band.lpc—is_banned()(~line 149),create_char_banned()(~168),is_strict_banned()(~187) short-circuit return 0 for loopback;allow_multi_login()(~242) returns 1000 for loopback.adm/daemons/logind.lpcencoding()— newlocal_connflag; the!ip_namedestruct and the "Non_number" character-scan destruct (~lines 158-175) are skipped for loopback/malformed IPs.adm/daemons/ipd.lpcseek_ip_address()— returns "本地连接" for loopback/short IPs instead of the (previously cosmetic-only under WASM)explode(ip,".")[1]out-of-bounds throw.adm/daemons/securityd.lpcmatch_wiz_site()(~line 97) — loopback always passes wizard site restriction.- No
uptime()startup gate, no multi-login cap use, and no reconnect throttle exist in this lib's logind (checked).
Admin account: fluffos / Mud@2026, Chinese name 浮浮, granted
(admin) via /adm/etc/wizlist (file normalized CRLF→LF). Verified:
registration via the real flow (login banner even shows 目前权限:(admin)),
re-login, update /d/city/kezhan.lpc succeeds. Saves at
data/login/f/fluffos.o + data/user/f/fluffos.o (not gitignored).
Fresh normal registration (秦风/testqa, female branch) re-verified
end-to-end and the test char removed; debug.log clean.
Fail-closed retrofit (2026-07-24)
The loopback carve-out above originally ALSO treated any empty/
non-string/unparseable IP as trusted-local (fail-open, defensive against
an older WASM query_ip_number() bug now fixed upstream). Tightened to
strict loopback only ("127.0.0.1", "::1", "127." prefix) across
band.lpc (×4), securityd.lpc's match_wiz_site, ipd.lpc (falls back
to "未知地区" for unparseable input instead of "本地连接"), and
logind.lpc's local_conn flag. Re-verified loopback login/look/update/
quit all still work after tightening.
深度功能测试(第二轮,2026-08-03)
之前的会话只测过注册流程,本轮做了完整的 §10.7 深度功能测试。
proactive 检查 AGENTS.md 已归档的四类常见坏味道:feature/
command.lpc 里的"private nomask command_hook"只是一行历史注释
(// private nomask int command_hook(...)),真正生效的声明早已
是干净的 nomask int command_hook(...);命中了一处 adm/daemons/
logind.lpc 里的 printf("%O") 调试残留(新增 mohuanshiji 到
AGENTS.md §7.34 确认实例列表);未命中 unguarded MESSAGE_D->、
stat/water 键名、§8.9 坏 age 判断。
完整流程验证:注册全新角色(沐魔幻/id mhsjqin,途中还发现
了一步之前没记录过的"您是否是中小学学生或年龄更小?(yes/no)"年
龄自述关卡——答 yes 或不理会直接被系统劝退,答 no 才能继续注册,
纯属游戏自带的内容提示,不是 bug),从"南城客栈"(同一个西游记
ES2 大家族的经典布景,唐僧、店小二、千里眼 NPC 驻场)出发,向
"朱雀大街"的"疥顶小僧"(这批西游记题材姊妹档案里反复出现的同一个
高战力 NPC,combat_exp 50000)发起战斗——完整交手数回合后被击昏
迷致死,送往"阴阳界"由"朱笔判官 崔珏"接引(和 yszz/bmxkx2001
是同一套死亡/复活设计),静候片刻后自动复活还阳,score 数据完
整(气血显示"重伤"正在恢复中,其余状态正常);quit 干净退出。
全程 debug.log 零报错。
顺带核实一处文档与实际行为不一致,判断为陈旧帮助文档而非 bug,
未做代码改动:doc/help/newbie 第二条明确写着"刚进入时,你的
食物和饮水都是空的",但实测新角色食物/饮水槽创建时就是满的
("正常"状态)。查了 logind.lpc 里的赋值语句——
user->set("food", user->max_food_capacity())
/user->set("water", user->max_water_capacity())——这是一行无条
件执行、没有任何坏掉的前置判断的正常代码,明确就是设计成"创建时
直接给满",不是 §8.9 那种"卡在错误物件的判断条件"式 bug。判断为
帮助文档没有跟上后来的某次平衡性调整,按项目惯例不去改动这行明
确、正常工作的代码去迁就一份过时文档。
未覆盖范围:拜师、留言板、当铺/兵器铺购物因时间原因未实测。
§7.86 跨库扫描修复(留言板 post 崩溃)
BBS_BOARD、BULLETIN_BOARDinherit+ 多余replace_program()致命形状(AGENTS.md §7.86,post命令崩溃):全档案 35 处命中,已删除多余的replace_program(...)调用(保留inherit),逐文件保留原有行尾格式(CRLF/LF 按文件原样)。本次为跨库 §7.86 扫描修复(触发原因:该 bug 已在 6+ 个互不相关的血统家族独立确认,属于近乎普遍的拷贝粘贴模式),仅做编译检查(驱动干净启动、端口正常监听),未做完整 §10.7 深度游玩测试。
§7.100 sweep (2026-08-19)
Fixed the corpus-wide inherit ROOM; ... replace_program(ROOM); redundant-replace bug (AGENTS.md §7.100). 274 live occurrences deleted: 269 via scripted sweep (fix_710_room.py), plus 5 hand-fixed roommaker-tool occurrences across 3 tool copies (clone/misc/roommaker.lpc, obj/roommaker.lpc — simple variant; cmds/wiz/roommaker.lpc — "room_code"/str 3-occurrence variant). 7 already-commented-out instances left untouched. One occurrence NOT fixed: d/city/center2.lpc is already syntactically broken independent of this bug (unquoted strings like set(short, 十字街头), stray ?? token, no semicolons at all in create()) — never compiles regardless, out of scope. No real .lpc source found under work/data/. Verified via build-debug driver boot: clean compile, port 40092 listening, zero new "cannot replace"/"cannot bind" debug.log lines. Pre-existing untracked test-account debris (data/{login,user}/m/mhsj{dive,qin}.o) confirmed left untouched.
Round-four re-test (2026-08-20): 拜师/留言板/当铺·兵器铺 + standing-checklist sanity pass
Covered exactly the three areas round-two/round-three had explicitly left untested for time, plus a sanity re-check of the standing bug catalog items called out for this pass.
Checklist sanity pass (all confirmed already correct/intact, no regressions, nothing new needed):
- §7.90 (eval-cost) —
config.fluffosstill hasmaximum evaluation cost : 5000000, intact. - §7.100 — re-confirmed intact (see sweep entry directly above).
- §7.111 (
standard_trace()/report_error()unguardedfile_name(error["object"])) — both call sites inadm/obj/master.lpcalready have the(undefinedp(error["object"]) || !error["object"]) ? "(none)" : file_name(error["object"])guard from the corpus sweep. - §7.112 (
init()call_outreconnect-duplication) — all 4d/death/npc/{wgargoyle,bgargoyle,pang,b}.lpcdeath-desk NPCs already guard with thedeath_stage_activetemp flag. - §7.113 (netdead reconnect losing
heart_beat) —obj/user.lpc'sreconnect()unconditionally doesset_heart_beat(1), andadm/daemons/logind.lpc's driver-invokedreconnect()callsuser->reconnect()unconditionally; correct lineage, no bug. - §7.114 (
privateinput_to()mixin callback) —feature/edit.lpc'sinput_line()is plain (notprivate); live-verified below with a real multi-line board post. - §7.115 (missing
QUESTmacro target) —include/globals.h'sQUESTpoints at/std/quest, andstd/quest.lpcgenuinely exists in this archive. Not applicable. - §7.79 (bare 2-arg
addn/addn_temp) — this lib has zeroaddn(/addn_temp(call sites anywhere in the whole tree (grepped), and no localaddndefinition either. Not applicable — this lib was never in the affected lineage.
1. 拜师 (sect apprenticeship) — mechanism confirmed working (clean
rejection). Located the command (cmds/std/apprentice.lpc) and a
reachable, no-existing-family-required sect master, 秦琼 (d/jjf/npc/
qinqiong.lpc, in d/jjf/keting正厅). apprentice qin (id qin, not
qinqiong — the alias list is qin qiong/qin/shubao/etc., space-
separated multi-word ids only) correctly resolved the target and
triggered attempt_apprentice(), which rejected on the admin test
character's insufficient combat_exp (< 100000) with an in-character
reason ("这位小兄弟还是先去跟本府家将打打基础吧!") — exactly the
"reject with a sensible reason" clean-pass shape. Re-verified identical
on a completely fresh driver boot. No crash, no debug.log/boot-log
error either time.
2. 留言板 (board posting) — confirmed fully working. post board at
南城客栈留言板 (/obj/board/nancheng_b.lpc, inherit BULLETIN_BOARD
with no stray replace_program() — matches the already-applied §7.86/
§7.100 fixes) entered the line-editor cleanly; two content lines plus a
title line were all accepted (no "什么?" misroute on any line, ruling
out the §7.114 shape), . ended the post with "留言完毕。", and read 1
board immediately after showed the full multi-line content correctly
saved with the right author/timestamp. Persisted correctly across a full
driver restart. Test post removed afterward via the in-game discard 1
board command (admin-authored, cleanly deletable) to leave the board
back in its pre-test empty state — data/board/ directory removed
after, no residue.
3. 当铺 (pawnshop) / 兵器铺 (weapon shop) — REAL BUG FOUND AND FIXED.
当铺 (/d/city/dangpu.lpc, inherit HOCKSHOP) worked cleanly:
value/pawn/sell/retrieve <stamp> all round-tripped correctly on
a cloned test weapon (典当 480 银, 卖断 640 银, 赎回 560 银 — sane,
consistent conversion math each time). list correctly reported "当铺
目前没有任何货物可卖" since this pawnshop has no vendor_goods of its
own (by design — it only deals in player-pawned items, not a curated
sale list).
兵器铺 (/d/city/bingqipu.lpc) crashed hard: list (i.e. simply
browsing the shop's stock, the very first thing any player does before
buying) threw an uncaught driver-level error on every single call:
执行时段错误:*call_other() couldn't find object '/clone/armor/whip'.
程序:/feature/vendor.lpc 第 48 行Root cause: feature/vendor.lpc's do_vendor_list() does
goods[name[i]]->query("name") on each vendor_goods string value
directly (an implicit lazy-compile call_other), with no existence
guard — and every one of the vendor 萧萧's (d/city/npc/xiaoxiao.lpc)
10 vendor_goods entries pointed at /clone/armor/<item>, none of
which resolved, because the whole directory on disk was
clone/ARMOR/ (uppercase) with 14 of its 21 item files ALSO carrying an
uppercase basename (WHIP.lpc, SWORD.lpc, MACE.lpc, etc.) — the
exact same "Windows-authored uppercase directory/filename vs. a
lowercase path baked into the referencing code" shape this lib's own
adm/daemons/CHANNELD.lpc-vs-CHANNEL_D bug already documented above,
just one level up (a whole directory, not one file) and hitting a much
more central, always-reachable code path (list in ANY shop using this
NPC's item catalog) instead of a rarely-exercised daemon call site. This
made the entire 兵器铺 permanently unusable for every player — list
never worked, and buy/value on any of its 10 items would have hit
the identical unguarded -> call in buy_object()/complete_trade()
the moment anyone tried.
Fix: renamed clone/ARMOR/ → clone/armor/ and the 14
uppercase-basename files inside it to lowercase (bang.lpc,
blade.lpc, dun.lpc, fork.lpc, jinjia.lpc, kui.lpc, mace.lpc,
pao.lpc, pifeng.lpc, shoes.lpc, spear.lpc, staff.lpc,
sword.lpc, whip.lpc, zhi.lpc) — all via git mv, content
untouched, matching every /clone/armor/<lowercase-id> reference
already in the code (corpus-wide grep confirmed exact 1:1 coverage, no
orphaned or newly-broken references either direction).
Extended sweep of the same bug class, whole clone/ tree. Given
this was a real, live-reachable crash and the fix pattern is mechanical
and low-risk, ran the same "uppercase-basename file/dir vs. a lowercase
reference elsewhere in the corpus" check across all of clone/ (not
just armor/), cross-referencing every /clone/... string literal in
every .lpc/.h file corpus-wide against the actual on-disk filenames.
Found 31 more genuinely-broken (referenced-but-case-mismatched)
files, all suffering the identical fate — silent, unconditional
->query()/new() crashes the moment anything actually touched them
(NPC weapon-equip calls, gift-item grants, event/魔幻-item spawns):
clone/{dan,qinghong,jiasha,tiekui,yinjia,bi,dun}.lpc (top-level),
clone/gift/{lingzhi,bingtang,xisuidan,xiandan}.lpc,
clone/bq/bang.lpc, and 18 files under clone/mohuan/
({spear,qin,fork,armor,jindao,pifeng,staff,whip,cloth,shoes,shendan,
sq,qingting,dun,mobang,axe,kui,sengxie,xiandan}.lpc). All renamed the
same way (git mv, content untouched). Re-ran the full corpus-wide
scan afterward: zero remaining case-mismatched /clone/...
references anywhere in the lib. (A separate, larger set of /clone/...
references — d/hen/-adjacent weapon items, d/wuguan/'s
CLOTH_OB/WEAPON_OB macro targets, a handful of /clone/gift/ and
/clone/book/ paths — genuinely have no on-disk file under ANY case
variant; left alone as pre-existing content gaps, not this bug.)
Verification: live-tested end-to-end on a freshly rebooted driver
(new PID, full recompile) — 兵器铺's list now shows all 10 weapons
with correct prices, buy mace from xiao completed a real gold-for-item
transaction (deducted the listed price, item appeared in inventory), and
the full 当铺 value/pawn/sell/retrieve cycle was re-run
end-to-end on the purchased item with correct currency math throughout.
Zero errors in the boot log across both driver runs (pre-fix crash was
player-visible in-session; post-fix, list/buy on this and every
other renamed item path produced no error at all). Driver killed by
exact PID (kill -TERM, not pkill) after testing. Admin account
(fluffos) inventory/gold left in its post-test state (matches this
lib's established convention of leaving the working admin seed account
as a normal, actively-used account); the synthetic board test post and
its resulting data/board/ save file were both removed, and the only
remaining untracked debris is the pre-existing mhsj{dive,qin} test
account pair already documented above (left untouched).
§7.30 uninitialized-mapping accessor sweep (2026-08-20)
Corpus-wide mechanical sweep of the feature/skill.lpc shared-lineage
bug (confirmed independently on xiakexing2017/jqxz2015/haiyang2
via round-four testing): 3 accessor(s) in this file returned a raw
never-initialized mapping instance variable (defaults to int 0,
not ([]), until first assigned), crashing any unguarded
keys()/sizeof()/indexing caller for a fresh/untrained character.
Fixed at the accessor level (mapp(x) ? x : ([])) per the documented
remedy. Verified via lpcc --batch static compile check only (not a
live boot) as part of a large mechanical sweep; not individually
functionally re-tested live on this lib.
Round-four re-verify (2026-08-24): re-ran 拜师/留言板/兵器铺, found and fixed a real §7.117 instance
The task brief for this pass named the same three areas as the
2026-08-20 entry above (拜师/留言板/当铺·兵器铺) — that entry already
covers them and fixes the 兵器铺 case-mismatch crash. Re-verified all
three live on a fresh driver boot before doing any new work, since the
brief also asked to specifically check for AGENTS.md §7.117 (the
apprentice/bai/recruit first-time-applicant guard gap, documented
after the 08-20 pass) — which the 08-20 entry did not check for.
Re-confirmed clean, no regressions: NPC-rejection path
(apprentice qin at low combat_exp, exact same 秦琼/d/jjf/keting
setup as before) still gives the same clean in-character rejection;
board post/read/discard round-trip at 南城客栈留言板 still works
end-to-end; 兵器铺's list/buy still work (clone/armor/ stays
lowercase, no case-mismatch regressions found anywhere under clone/).
Found and fixed a real §7.117 instance. cmds/std/apprentice.lpc's
"the target already offered to recruit me, I'm confirming" branch (the
ob->query_temp("pending/recruit") == me branch, entered when a player
runs apprentice <master> a second time after the master's
attempt_apprentice()/recruit accepted them) compared
me->query("family/family_name") != ob->query("family/family_name")
with no guard for me having no family yet — exactly the pattern
in AGENTS.md §7.117. For any legitimate first-time apprentice (the
overwhelmingly common case — everyone's first sect join goes through
this exact code path), me->query("family/family_name") returns 0
(no family set), (string)0 casts to "0", which is never equal to
the master's real family name, so the check always misfires into the
"betray your old sect" branch: wrong flavor text ($N决定投入$n门下
instead of the correct $N决定拜$n为师), plus me->set("score", 0)
and me->add("betrayer", 1) incorrectly applied to a brand-new
apprentice who never had a prior sect to betray.
The sibling file cmds/std/recruit.lpc (the master's side of the same
recruit/confirm transaction) already has the correct guard at its
analogous check (line 63: if ((ob->query("family")) && (...))), with
an explicit comment // follow modified by elon 09-10-95 to fix a bug
in 1st time recruit acknowledging this exact bug class — the fix was
applied once to recruit.lpc decades ago and never ported to its twin,
apprentice.lpc. Matches the general §7.117 write-up's own observation
about this exact half-fixed-pair shape.
Fix (cmds/std/apprentice.lpc, in the recruit-confirm branch):
// before:
if ((string)me->query("family/family_name") != (string)ob->query("family/family_name")) {
// after:
if ((me->query("family")) && ((string)me->query("family/family_name") != (string)ob->query("family/family_name"))) {Live-verified end-to-end on a fresh driver boot: bumped a
family-less admin test character's combat_exp to 200000 (temporarily,
via a throwaway wizard command created and removed within this
session — not left behind), went to 秦琼 at d/jjf/keting, ran
apprentice qin twice (first call triggers attempt_apprentice() ->
NPC recruits the player; second call is the player's confirm). Before
the fix this would have shown the wrong "決定投入...門下" betrayal
message and zeroed score; after the fix it correctly shows "秦琼决定
收你为弟子。...恭喜您成为将军府的第三代弟子。", and score correctly
shows 师承:将军府秦琼 / 将军府第三代弟子. Reverted the test
character's family/title/betrayer/combat_exp/class fields
back to pristine afterward (also bought+destroyed one 兵器铺 item along
the way while re-verifying the shop — correct gold deduction, 19→11
两黄金 for an 8-两黄金 item — left the resulting gold balance as the
account's normal post-test state, matching this lib's existing
admin-account convention). lpcc --batch and a full driver reboot both
clean after the fix, zero new debug.log errors. No corpus-wide sweep
of this exact shape was attempted here (each instance needs hand
verification per the general §7.117 write-up); left for a future
dedicated sweep as already flagged there.
AGENTS.md §7.19: enable_player() reentrancy guard (2026-09-01)
Same corpus-wide bug class as mhxy/wuhanzhan: feature/command.lpc's
enable_player() wraps enable_commands() and is unconditionally
reachable from an NPC's init() via setup()/reset_me() (confirmed
on this lib's own d/*/npc/zhangmen*.lpc-family NPCs, matching
mhxy's originally-documented d/xueshan/npc/zhangmen.lpc pattern).
Calling enable_commands() on an object that's already living()
makes the driver re-invoke that object's init() as a side effect;
since init() calls back into enable_player(), that is genuine
same-call-stack reentrancy that repeats until "Too deep recursion"
aborts a room's first-ever visit.
Fixed with a true reentrancy flag (nosave private int
in_enable_player_now;), NOT a bare if (living(this_object()))
return; guard — this lib's feature/damage.lpc revive() and
cmds/std/sleep.lpc wakeup()/wakeup2() all legitimately
re-invoke enable_player() while the object is still living()
(that's how a fainted/asleep character gets commands back), so a
living()-gated guard would silently break every one of those real
re-enables. enable_player()'s single body has no early return
statements, so the flag is set at entry and cleared once, before the
function's fall-through end. Verified with a single-file lpcc
compile check (exit 0, no errors) against feature/command.lpc.