Myth of the Three Realms (Wisteria Branch)

✅ 可玩

三界神话「紫藤分站」

sjshv150

🔑 fluffos / Mud@2026 更新 d122741 2026-09-01 源码 下载 ZIP

▶ 开始游玩 · Play Now

"三界神话"系列第二个档案(另见 `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.lpclog_error()/standard_trace()CHANNEL_D 尚未加载时呼叫它——两处都补上 find_object(CHANNEL_D) 判断。 2. §7.61 message() 模拟超越函式缺了 exclude 参数的兜底: 一旦 CHANNEL_D 加载成功,channeld.lpcdo_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.lpcis_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.lpcget_wizpwd() 在从未设置过 WIZ 密码(注册流程根本不会设置,只能登入后用游戏内 WIZPWD 指令设置)时,只打印提醒就直接返回,既不放行也不重新 等待输入——先有鸡还是先有蛋的死结,导致 wizlist 里任何账号(不 限于 admin)从第二次连线起永远卡在"什么?",debug.log 无任何 记录。已改成提醒后直接 check_ok(user); return;,和这份档案自己 在首次登录成功后打印同一条提醒但完全不阻断的既有行为保持一致; 死亡→复活→重新登录的完整状态保留已重新验证通过。 7. (§7.34 debug 残留,已清) adm/daemons/logind.lpcget_name() 里一行裸 printf("%O\n", ob),会把内部对象路径打印 在中文名字确认和密码提示之间——已删除。 8. (§7.11 缺失目录防护,已补) adm/simul_efun/file.lpclog_file() 没有调用同文件里现成的 assure_file(),几个日志路径 (如 securityd.lpc 授权日志用的 /log/nosave/)在这份档案里从 未随仓库分发——已在写入前补上 assure_file() 调用。

管理员账号 / Admin account

警告:对外公开架设前请务必修改此密码。

本地运行

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 崩溃)

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

用原生驱动(build-debug/src/driver,端口 40171)通过 scripts/tmux_mud.shscripts/mudclient.py 走完整轮,对照同家族已深挖的 sjsh(宝鸡站)NOTES.md 逐条核对。

``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。

管理员账号(fluffos/Mud@2027,管理密码 Mud@2026)本次通过正常注册流程首次真正落地——此前 adm/etc/wizlist 已有 fluffos (admin) 一行(更早阶段播种),但 data/login/f/fluffos.odata/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 走了一轮针对性验证。

驱动全程 debug.logwork/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.