info.html · 介绍 · play.html · 游玩 · llms.txt · 下载与本地运行 · ZIP · GitHub
"三界神话"系列第二个档案(另见 `sjsh` 宝鸡站原始版),紫藤分站的内容,核心代码库与 `sjsh` 同源,但除去存档/日志差异后两者档案级别只有约四分之一逐字节相同——是内容上真正独立的一次开发,而非近乎重复的部署。世界观是武侠门派与中国神话仙界的混搭:地图里既有武当、华山、古墓、蜀山、雪山等常见武侠门派场景,也有"三十三天""十二宫""二郎神""蟠桃""蓬莱""轮回""皇宫"等神话地名,"三界"(天界/人间/幽冥)之名由此而来。开封城是一整套解谜任务区(几十个场景,含当铺、军器铺、东湖 1-8 号等),是这份档案在原三界神话系列里相对成熟、后来仍被"楚天站"沿用的内容。死亡/复活流程完整:死后由判官 `崔珏` 在〖阴阳界〗主持"翻生死簿"仪式,复活地点是荒郊小店,同一间小店的"生死之间留言板"支持正常 `post`/`read`。游戏自带的说明文字坦承"本版本里 bug 如云,而且有一个已知后门(不严重)"——这段自嘲式声明是原作者留下的自述,本身也算这份档案的历史特色之一。
English
The second build in the "Myth of the Three Realms" series (see also sjsh, the Baoji-station original) — the Wisteria-branch server's content, sharing sjsh's core codebase but only about a quarter byte-identical to it at the file level once player-save/log churn is excluded, i.e. a genuinely distinct content snapshot rather than a near-duplicate. The setting mashes standard wuxia sect geography (Wudang, Huashan, the Ancient Tomb, Shu Mountain, Snow Mountain) with Chinese-mythology locales — the Thirty-Three Heavens, the Twelve Palaces, Erlang Shen, the Peach of Immortality, Penglai, the Wheel of Reincarnation, the Imperial Palace — which is where the "Three Realms" (Heaven/Human/Underworld) title comes from. Kaifeng city (d/kaifeng/, ~170 files) is a substantial standalone puzzle-quest zone with its own pawnshop and weapon shop, later reused wholesale by the unrelated "Chutian" server. Death sends the player to Judge Cui Jue in the Yin-Yang Realm to review the Book of Life and Death before respawning at a wayside inn, whose message board supports normal post/read. The game's own bundled notes candidly admit it "still has bugs galore, including one known (minor) backdoor."
README
本次修复的关键 bug
和 sjsh 大部分相同(同源代码),另外还有两个这份档案独有的:
1. §7.60 master.lpc 的 log_error()/standard_trace() 在
CHANNEL_D 尚未加载时呼叫它——两处都补上
find_object(CHANNEL_D) 判断。
2. §7.61 message() 模拟超越函式缺了 exclude 参数的兜底:
一旦 CHANNEL_D 加载成功,channeld.lpc 的 do_channel() 只用
3 个参数呼叫 message()(exclude 留空默认为整数 0),触发
*Bad argument 4 to EFUN message()——已在
adm/simul_efun/message.lpc 里改成
efun::message(arg, message, target, exclude || ({}))。
3. 和 sjsh 相同的 44 行 convertd.lpc 损坏字节表格、emoted.lpc
未加保护的 restore()。
4. is_chinese()/check_legal_name() 的字节配对假设在 UTF8 下失
效:check_legal_name() 用 i%2 作为奇偶配对检查(假设 GBK
每字 2 字节),但这个驱动下字符串是按码点索引的,每个中文字算 3
字节——导致中文名字字数为奇数时 i%2 恒真,永远被拒绝,只
有偶数字数的名字才凑巧能通过。已改成按码点检查的 is_chinese()
加上正确的 1-6 字长度上限(去掉 i%2 判断)。
5. 仅限巫师从本地回环地址登录的限制连"new"这个关键字本身都会挡
住:adm/daemons/sited.lpc 的 is_valid()(和 sje 那份形状
相同)只允许巫师身份的 id 从 127.0.0.1 登录,但因为 new(触
发注册的关键字)本身永远不是巫师,导致本地/WASM 测试环境下完
全无法开始注册流程。已比照这份档案自己已有的 allenc 硬编码
例外,追加 id=="new" 例外——这属于 §1.3e 已经确立的"仅影响本地
测试环境的额外摩擦"这一类,对真实远程部署没有任何影响(真实玩家
永远不会从 127.0.0.1 连过来)。但陌生的(不在 wizlist 里的)全新
id 依然无法从 WASM/本地环境注册成功,这是一个真实但范围很窄的测
试限制,本次没有进一步处理。
6. (§10.7 深度测试新发现,AGENTS.md §8.13)WIZ 密码二次登录闸门
永久卡死 wizlist 账号:adm/daemons/logind.lpc 的 get_wizpwd()
在从未设置过 WIZ 密码(注册流程根本不会设置,只能登入后用游戏内
WIZPWD 指令设置)时,只打印提醒就直接返回,既不放行也不重新
等待输入——先有鸡还是先有蛋的死结,导致 wizlist 里任何账号(不
限于 admin)从第二次连线起永远卡在"什么?",debug.log 无任何
记录。已改成提醒后直接 check_ok(user); return;,和这份档案自己
在首次登录成功后打印同一条提醒但完全不阻断的既有行为保持一致;
死亡→复活→重新登录的完整状态保留已重新验证通过。
7. (§7.34 debug 残留,已清) adm/daemons/logind.lpc 的
get_name() 里一行裸 printf("%O\n", ob),会把内部对象路径打印
在中文名字确认和密码提示之间——已删除。
8. (§7.11 缺失目录防护,已补) adm/simul_efun/file.lpc 的
log_file() 没有调用同文件里现成的 assure_file(),几个日志路径
(如 securityd.lpc 授权日志用的 /log/nosave/)在这份档案里从
未随仓库分发——已在写入前补上 assure_file() 调用。
管理员账号 / Admin account
- ID:
fluffos - 管理密码 / Admin password:
Mud@2026 - 普通密码 / Regular password:
Mud@2027(这份档案要求管理密码 和普通密码不能相同,是这份档案自己的注册规则,故普通密码另设) - 权限 / Level:
(admin),通过/adm/etc/wizlist授予——同时 也是绕过上面第 5 条本地登录限制所必需的账号。日常登录用普通密码Mud@2027;管理密码Mud@2026仅在普通密码遗失时用于找回。
警告:对外公开架设前请务必修改此密码。
本地运行
cd libs/sjshv150
~/src/fluffos/build-debug/src/driver config.fluffos游戏端口:40171。
NOTES · 移植与修复记录
WASM 修复摘要(迁移自 meta.json 的 group_note)
三界神话「紫藤分站」,5 档案 sjsh 家族集群的第二个;和 sjsh(宝鸡站)子分支内容不同(紫藤 vs 宝鸡)但共享同一套核心代码库。WASM 修复:(1)§7.60 master.lpc log_error()/standard_trace()→CHANNEL_D 编译期崩溃,两处都用 find_object(CHANNEL_D) 守卫——这里有这个 bug,不像 sjsh 那份具体变体没有。(2)和 sjsh 一样的 45 行损坏字节 convertd.lpc 表,用完全相同的字节级脚本修复。(3)§7.61 message() simul_efun 包装函式缺少 exclude||({}) 守卫——channeld.lpc 的 do_channel()(以及其它 3 参数呼叫 message() 的地方)在修好(1)之后、CHANNEL_D 真的成功加载时崩溃报"Bad argument 4 to EFUN message()";这正是 AGENTS.md §7.61 已经记载的那个 channeld.lpc 呼叫点。(4)§7.41 类损坏的 emoted.o,和 sjsh 相同的 catch(restore()) 修法。(5)§8.1 类的 is_chinese() 字节区间 bug,加上一种没减半长度界限类的不寻常表现:check_legal_name() 用 i%2 作为奇偶门槛,假设每个中文字占 2 字节 GBK,而这个驱动下 UTF8 码点索引的字符串里每个中文字占 3 字节,导致字数为奇数的 CJK 名字永远被拒绝(i%2 恒真),偶数字数则碰巧能通过——已修好 is_chinese()(码点区间检查)和 check_legal_name() 的界限(1-6 字符,匹配提示文字,去掉 i%2 门槛)。(6)一个仅限巫师的本地回环注册闸门(adm/daemons/sited.lpc 的 is_valid(),和 sje 的形状相同)连字面的"new"注册关键字都会挡在 127.0.0.1 之外,因为 wiz_level('new') 永远不为真——已专门为"new"加了例外(和这份档案自己既有的"allenc"引导例外并列),符合既定的 §1.3e 本地测试摩擦豁免类;真正全新的(未在 wizlist 里的)玩家 id 仍然无法从 WASM/本地环境注册,这是一个真实但范围很窄的测试限制,没有进一步处理,因为真实的远程部署不受影响(非本地 IP 永远不会走到这个回环分支)。管理员账号播种:fluffos (admin) 加入 adm/etc/wizlist——这个账号同时绕过了回环 id 闸门(已经是巫师)和双密码(管理+普通)注册流程里的第二次 is_valid() 复查。已验证:完整注册(new→id→名字→管理密码→确认→普通密码→确认→电子邮件→性别→赠礼)→进入游戏世界,权限正确显示 (admin);default_trusted_write/default_exclude_write ACL 表也已直接核对源码确认授予 (admin) 不受限的"/"写入权限。LPC 格式化工具对全部 10310 个档案运行;还原了 2 个通过"去空格后比对旧档案"扫描(覆盖 113 个格式化工具触碰过的档案)确认有 CJK 重新加空格损坏的档案(和 sjsh 相同的 2 个档案,共享内容);另外直接比对了唯一一个 map.lpc 档案——干净,只是排版调整。格式化后重新验证过,干净。
§7.86 跨库扫描修复(留言板 post 崩溃)
BULLETIN_BOARDinherit+ 多余replace_program()致命形状(AGENTS.md §7.86,post命令崩溃):全档案 45 处命中,已删除多余的replace_program(...)调用(保留inherit),逐文件保留原有行尾格式(CRLF/LF 按文件原样)。本次为跨库 §7.86 扫描修复(触发原因:该 bug 已在 6+ 个互不相关的血统家族独立确认,属于近乎普遍的拷贝粘贴模式),仅做编译检查(驱动干净启动、端口正常监听),未做完整 §10.7 深度游玩测试。
深度功能测试(§10.7,2026-08-08)
用原生驱动(build-debug/src/driver,端口 40171)通过 scripts/tmux_mud.sh 和 scripts/mudclient.py 走完整轮,对照同家族已深挖的 sjsh(宝鸡站)NOTES.md 逐条核对。
- §7.97(sjsh 上发现的 LISTNODES 死亡死循环 bug)——不适用,已核实排除:
work/include/net/config.h第 19 行#define LISTNODES ([ \本身就带着正确的续行反斜杠(cat -A确认行尾是\$,不是裸$),跟 sjsh 那份缺失反斜杠的版本形状不同——这两份档案虽然共享核心代码库,但这个头文件的具体内容不是逐字节同源(diff两份config.h显示是完全不同的文件版本:sjshv150 是"神话世界·西游记·版本4.50"版本头,LISTNODES 表项也不同:SJSH-DBSZ/SJSH_SK/SJSH_JYG/SJSH_SD,而 sjsh 是SK/BJ/SD)。为求实证而非只信静态分析,仍然用管理员测试角色在朱雀大街对疥顶小僧(d/city/npc/jieding.lpc)打kill到真实死亡:屏幕只打印一次"你死了",系统频道正常广播"〖谣言传说〗某人:测试道人在长安城被疥顶小僧杀死了。",角色被送进〖阴阳界〗(/d/death/gate),判官崔珏(朱笔判官 崔珏)自动完整走完"莫乱跑→生死有命→翻生死簿→命不该死送还阳"对话,角色活着落地复活室〖荒郊小店〗(/d/ourhome/kedian,气血:重伤,被杀害次数正确记为 1),全程debug.log干净无dns_master/gchannel崩溃痕迹。确认本档案没有这个 bug。
- (新增 AGENTS.md §8.13)
adm/daemons/logind.lpc的 WIZ 密码二次登录闸门在从未设置过 WIZ 密码时没有成功路径,导致 wizlist 内任何账号第二次登录起永久卡死:这是本次深挖发现的、比 §7.97 更容易被忽视的新 bug——它只在重新登录(restore 路径)时才会触发,注册后紧接着的第一段会话完全不会经过这段代码,而 AGENTS.md §10.1/§10.7 恰恰要求重新登录也要至少验证一次,正是靠着这条规则才抓到。get_passwd()里,只要账号 id 在SECURITY_D->get_wizlist()里(不限于 admin,apprentice/wizard/arch 全部一样),就会额外要求输入"WIZ密码"(input_to("get_wizpwd", 1, ob)),但get_wizpwd()本身:
``lpc
if (!user->query("wiz_password")) {
write(HIW "你没有设定WIZ密码,请用WIZPWD来设定!\n" NOR);
}
if (user->query("wiz_password")) { ... }
`
在从未设置过 wiz_password(注册流程完全不会设置它,只能靠登入后用游戏内 WIZPWD 指令手动设——这就是先有鸡还是先有蛋的死结)的情况下,第一个 if 打印提醒后直接落空——既不调用 check_ok() 进入游戏,也不重新 input_to(),函数就这样返回。玩家紧接着输入的任何东西(look)都落进了裸连接对象自己的通用失败回复,永远只会收到"什么?",且 debug.log 完全没有任何记录(不是崩溃,是静默卡死)。用刚播种好的管理员测试账号 fluffos 实测复现:第一次注册会话一切正常(这条闸门根本没被走到);quit 后用 scripts/mudclient.py 重新连线、走完 id+密码流程,看到"你没有设定WIZ密码,请用WIZPWD来设定!"提示后,look/score 全部只回"什么?",永远进不了游戏世界。修复:把提醒分支改成非阻断——补上 check_ok(user); return;,和这份档案自己在首次登录成功后(enter_world 之后同样打印这条提醒但完全不阻断)的既有行为保持一致。重启驱动后重测:同样的重连流程打印相同提醒,随即正常进入游戏(落地〖巫师会议厅〗,因为 fluffos 已是巫师),look/score/quit` 全部正常,此前死亡记录(被杀害 1 次、气血重伤)也正确保留,证明存档读取完全没问题,唯一坏掉的只是这道 WIZ 密码闸门本身。已更新 AGENTS.md,新增 §8.13。
- §7.34(logind.lpc 遗留 debug printf)适用,已修:
get_name()在ob->set("name", arg)之前有一行裸printf("%O\n", ob);,会把登录对象内部路径(/obj/user/login#0 ("0(fluffos)"))原样打印在中文名字确认和密码提示之间——用fluffos账号首次注册时现场复现确认(修复前的截图证据)。已删除该行;修复后重启驱动,用sited.lpc里既有的allenc本地测试豁免账号重新走一遍完整注册流程(new→allenc→中文名字→两套密码→邮箱→性别→天赋→落地),全程 grep 确认没有任何login#/obj/user字样泄漏;测试用的allenc存档已在提交前删除(data/login/a/allenc.o、data/user/a/allenc.o,未纳入 git,直接rm,不影响任何其它账号)。
- §7.11(log_file 缺 assure_file 防护)适用,已修:
adm/simul_efun/file.lpc的log_file()是裸seteuid(ROOT_UID); write_file(LOG_DIR + file, text);,同一份档案里的assure_file()辅助函数从未被调用;ls work/log/确认nosave/目录本档案确实没有随仓库分发(会被securityd.lpc::set_status()的log_file("nosave/promotion", ...)、崩溃处理器的log_file("nosave/CRASHES", ...)等路径触发写入失败),和 sjsh 已记载的形状完全一致。修复:补一行前向声明void assure_file(string file);(log_file()在文本顺序上定义在assure_file()之前),并在write_file()前调用assure_file(LOG_DIR + file)。防御性修复,本轮实际游玩没有直接触发这几条日志路径,但按既定套路"看到就修"。
- §8.9(食物/饮水初始化)不适用:
confirm_gift()直接user->set("food", user->max_food_capacity())/user->set("water", user->max_water_capacity()),没有对象混用、没有年龄闸门。三个测试角色(fluffos/allenc)score食物/饮水都是满格「正常」。
- §7.88/§7.12(message() varargs 缺陷)不适用,此前一轮已修好:
adm/simul_efun/message.lpc的message()已经是efun::message(arg, message, target, exclude || ({}))(对应 group_note 里记载的 §7.61 修复),tell_room()同样有exclude || ({})兜底;本轮死亡广播、频道消息等大量触发message()的路径全程零崩溃,确认修复依然有效。
- §8.3a(
private nomask command_hook)不适用:feature/command.lpc的command_hook()声明就是nomask int command_hook(string arg),没有private。
- §8.3b(
commandd.lpc的.c后缀 sscanf)不适用:整个档案没有commandd.lpc这个文件,指令分派走feature/command.lpc的add_action机制。
- §7.90(eval cost 上限)本次未观察到问题,
config.fluffos保持默认700000未改动:跨越注册、天赋分配、多次移动到未编译过的房间、两轮真实战斗到死、两次完整复活流程、post/read/update等操作,work/log/debug.loggrepcost limit reached/Too long evaluation均为 0。
- §7.5(securd/securityd 自定义 ACL 拒绝编译期访问,含 file_size 和 get_dir 两个变体)不适用:
adm/daemons/securityd.lpc的valid_read()已经显式if (func == "file_size") return 1;;本轮完整跑过注册、战斗、死亡、复活、post、update(recompile)等触发大量首次编译/首次读取的路径,work/log/debug.log、work/log/nosave/FILES(未生成,说明零次 ACL 拒绝)均无任何 "access denied"/"read attempt...failed" 记录。get_dir()用到的"stat"func 也未见任何异常(指令表本身工作正常,kill/look/post/update全部能正确分派)。
- §7.98(daemon
create()缺seteuid()导致自身配置读取被拒)不适用:全程debug.log没有出现任何explode()/sscanf()相关的 preload 期崩溃(sjshv150 启动日志本身也完全干净,无编译错误无运行时错误)。
- 管理员写权限已现场验证:用
fluffos账号执行update /d/city/kezhan,输出"重新编译 /d/city/kezhan.lpc ...成功!",确认securityd.lpc的default_trusted_write["/"] = (admin)确实生效。
- 留言板
post/read验证通过,§7.86 修复线上确认有效:在〖荒郊小店〗对"生死之间留言板"(common_a.o)成功post一条标题「深度测试」的留言并read 1读出,无崩溃;同一条内容还自动镜像进了〖巫师会议厅〗的留言板总汇(post_b.o,board_id: "post_b",std/misc/bboard.lpc的转发机制)。这两份留言板数据此前完全未随仓库分发(git ls-files确认data/board/目录此前不存在于版本控制里,说明该档案在本次深挖之前从未真正post过),本次测试留言在提交前已直接rm两个测试产生的.o文件(不是清空历史内容——本档案确实没有 sjsh 那样的历史留言)。
- 未覆盖:
buy/商店购买流程本轮时间有限未测(list指令本身也没顾上,架空于巫师会议厅的账号需要先走到普通城镇商店);帮派/门派拜师流程未触及;WIZPWD指令本身(用于设置本次发现要求非阻断的那个 WIZ 密码)未实测,只验证了"未设置时不再卡死"这一失败模式的修复,没有验证"设置后校验成功/失败"两条分支——这两条分支代码本身逻辑清晰(crypt()比对),风险较低,留待下次深挖时一并验证。
管理员账号(fluffos/Mud@2027,管理密码 Mud@2026)本次通过正常注册流程首次真正落地——此前 adm/etc/wizlist 已有 fluffos (admin) 一行(更早阶段播种),但 data/login/f/fluffos.o、data/user/f/fluffos.o 此前并不存在,说明账号只播种了权限数据、从未真正注册过。本次完整走完双密码注册流程后立即显示"系统权限目前是:(admin)";已更新 README 的"管理员账号"一节,把密码从"注册时自设"改为具体的 Mud@2026/Mud@2027(管理密码/普通密码不能相同,是这份档案自己的规则)。
§7.100 扫描修复(ROOM 基类多余 replace_program())
#define ROOM "/std/room/room"(嵌套路径,不要与其它以 /std/room
为宏值的手足档案混淆):删除 515 处多余的、独立成行的
replace_program(ROOM);(保留 inherit ROOM;),509 处脚本自动
删除;另有 6 处手动修正——obj/roommaker.lpc(1 处,标准"两套模
板"简单变体,字符串拼接模板第 139 行);此外,脚本的 data/ 目录
排除逻辑(用于保护玩家存档)意外漏掉了本库真实存放在
work/data/group/obj/ 下的两份帮派管理命令源码——ling-pai.lpc(2
处,do_saveroom() 分支)、ling.lpc(3 处,do_mkroom() 一处 +
do_saveroom() 两处),均确认是货真价实的 .lpc 源代码(帮派令牌
/令旗相关物品对象,内嵌造殿堂房间的命令),已逐一手动删除。修复后
全库仅剩 13 处历史遗留的 //-注释掉实例,均确认无害、未改动。已
用 build-debug 驱动干净启动验证(0 个新增编译错误,端口 40171
正常监听,debug.log 无新增 "cannot replace"/"cannot bind" 行);
未做完整 §10.7 深度游玩测试。
§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 补测(2026-08-24)——补齐先前"未覆盖"清单
复用同家族已知的两个 bug 检查、以及先前 §10.7 记录里明确留下的三项
"未覆盖"(buy/拜师/WIZPWD 两条校验分支),用 build-debug/src/driver
(端口 40171)+ scripts/tmux_mud.sh 走了一轮针对性验证。
- 已知 sibling bug #1(
combatd.lpckiller_reward()缺killer->add("PKD",1)/victim->add("DIE",1))——不适用:work/adm/daemons/combatd.lpc第 866-868 行三行齐全,本档案没有这个 缺陷(可能这份档案的 combatd.lpc 版本比 sjshwzb/sjshwzjqb 那份新,或 从未被那次局部删除影响到)。
- 已知 sibling bug #2(
d/lingtai/obj/shengmao.lpcset_name()未闭合字符串)——不适用:本档案确实有这份档案(work/d/lingtai/obj/ shengmao.lpc),但第 7 行set_name(HIC "三界神帽" NOR, ({...}));字符串引号完整闭合,不是 sjshwzb/sjshwzjqb 那个坏版本。
buy/商店购买流程——已测试,正常:用clone给管理员测试角色fluffos变出/obj/money/silver(一两银)后在〖三联书局〗 (/d/city/bookstore,老板孔方兄d/city/npc/bookseller.lpc)buy xyjbook from kongfang(单价一两银子,精确找零为零),成功买到 《西游记》,银子被正确扣光。再clone /obj/money/gold(一两金= 10000 值)购买十两银子(1000 值)的〖刀法入门〗,找零验证: 10000-1000=9000 值 → 应得九十两银(90×100=9000),实测背包正确出现 「九十两银子」——找零换算完全正确。list指令价目表显示也正常。 测试道具已在提交前用drop丢弃(房间有自动清除的绿色小精灵拾走, 不会残留在版本控制内)。
- 门派拜师(apprentice/bai)——测出并修复了一个真实 bug(AGENTS.md §7.117 同款):
cmds/std/apprentice.lpc第 62 行原本是if ((string)me->query("family/family_name") != (string)ob->query("family/family_name")) {——这正是 AGENTS.md §7.117 记载的、跨 74+ 库确认过的"拜师确认分支缺 首次拜师existence guard"的标准坏形状:当一个从未入过任何门派的新手 角色被师父recruit后再用apprentice确认拜师时,me->query ("family/family_name")是空值,天然就"不等于"师父的门派名,导致代码 误判为"背叛旧门派转投他派",本应是else分支的"正常拜师"礼却走进了 "背叛"分支(会清零 score、打上 betrayer 标记)。同一对指令的另一半cmds/std/recruit.lpc第 63 行早就有正确的(ob->query("family")) &&存在性守卫(还带注释"follow modified by elon 09-10-95 to fix a bug in 1st time recruit")——证明这个修复曾经打在配对文件的一半上,另一半 (apprentice.lpc)漏掉了,和 AGENTS.md §7.117 描述的典型情形分毫不 差。修复:按 §7.117 既定写法加上同款守卫——if ((me->query("family")) && ((string)me->query("family/family_name") != (string)ob->query("family/family_name"))) {。 现场复现+验证:clone 一个/d/city/npc/shubao(秦琼,将军府二代弟子,attempt_apprentice()无条件接受)放在管理员测试角色fluffos所在房间,用call shubao->command("recruit fluffos")让秦琼先对我发起收徒(设置秦琼自己的pending/recruit),这样后续apprentice shubao才会真正走到本档案原本有 bug 的那条"确认拜师"分 支(而不是走recruit.lpc那条本来就守卫正确的路径)。update /cmds/std/apprentice重编译成功后执行apprentice shubao,输出 "你决定拜秦琼为师...恭喜您成为将军府的第三代弟子。"(正常拜师文案, 不是"决定投入...门下"的背叛文案),score确认师承显示"将军府秦琼"、familydbase 字段里没有出现betrayer标记、combat_exp/score等属性也未被清零——修复确认生效。
WIZPWD校验成功/失败两条分支——已测试,均正常(此前一轮只验证 过"未设置时不再卡死"这一条修复,这两条从未测过):用fluffos/Mud@2027重新登录,先用wizpwd指令(提示文字用大写WIZPWD但实际指令是小写wizpwd,纯粹是大小写不敏感,不是 bug) 首次设定 WIZ 密码为TestWiz123;quit后重新连线,get_wizpwd()验证分支:(1)故意输入错误密码WrongPass——正确显示"密码错误! 请重新输入你的ID和密码!"并要求重新走 id+密码流程(未直接进入游戏); (2)随后用正确的TestWiz123——正确显示"密码正确!"并正常进入游戏 世界。两条分支的crypt()比对逻辑均按预期工作,没有发现新 bug。
驱动全程 debug.log(work/log/debug.log)未生成任何内容(说明零次
运行时错误/崩溃),work/log/err.log 只有既有的"Unused local
variable"编译期警告,无新增编译错误。测试产生的一次性登录日志
(u/koker/files/EEDIT 新增两行"fluffos(admin) sets/updates"审计记录)
按既定套路保留,不算需要清理的临时产物。已停止 tmux 会话与本地
build-debug 驱动进程。
AGENTS.md §7.19 sweep (2026-09-01): enable_player() reentrancy from init()
Same corpus-wide bug class as mhxy/wuhanzhan (AGENTS.md §7.19): this
lib's feature/command.lpc enable_player() wrapper (around the raw
enable_commands() efun) is reachable from an NPC's init() via a
redundant create()-then-init()-calls-setup() (or reset_me()
calling setup()) chain -- confirmed live via a static scan of every
init() body in this lib: 45 NPC/item files call setup() directly
or via reset_me() from init(), after create() already called
setup() once (which already made the object living()). Calling
enable_commands() a second time on an already-living() object makes
the driver re-invoke that object's own init() as a side effect, which
re-enters this same chain while the original call is still on the
stack -- genuine reentrancy, crashing with "Too deep recursion" (most
likely to surface on an NPC's first-ever preload/compile).
feature/damage.lpc's revive() and cmds/std/sleep.lpc's wakeup1()/wakeup2() call enable_player() again while the object is still living(). This confirms a bare if (living(this_object())) return;
guard would be the WRONG fix (it would silently break that legitimate
re-enable) -- used the same true reentrancy-flag fix as mhxy instead:
a nosave private int in_enable_player_now; set for the duration of the
wrapper's body, guarding only genuine same-call-stack reentrancy while
leaving every legitimate re-enable (revive/wakeup/disguise) unaffected.
feature/command.lpc's enable_player() had a single fall-through exit
(no early returns), so one guard-at-top + one clear-at-bottom pair was
sufficient. Verified via a single-file lpcc --batch compile check
(PASS) -- not individually live-boot-tested.