Rhapsody Realm II

✅ 可玩

狂想空间II

kxkjii2

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

▶ 开始游玩 · Play Now

ES II/Annihilator 系架构衍生的 mudlib,和 jyqxc 系列不是同一血统(存档用驱动原生的明文格式,不是那一系的旧式二进制格式)。角色创建走互动式点数分配:膂力/定力/悟性/胆识/根骨/灵性/容貌共 7 项从共享的 160 点数池里手动分配,第 8 项"福缘"由剩余点数自动填入。地图以新手区"雪村"(钱庄、当铺、客栈、药铺)起步,主线之外另有一片"过去时空"区域(水烟阁、山坡小径、青石官道)。权限体系比常规多一级,`(manager)` 是比 `(admin)` 更高的顶层职称,档案里两个内置管理员账号都是这个级别。与本项目的 `kxkj`("狂想空间")相比,`diff` 核对确认两者的 `master.lpc`、`securityd.lpc` 及整个死亡区目录逐字节完全相同——不是改编或续作关系,而是同一份 2002 年台湾巫师 wade 发布源码的两份独立存档快照,各自独立搭建了内容不同的同名"雪村"新手区,档案总数仅差 10 个(均为运行期存档差异,非源码)。

English

A mudlib derived from the ES II/Annihilator architecture line, a different lineage from the jyqxc family (it uses the driver's native plaintext save format rather than that family's old binary format). Character creation is a fully interactive point-buy: seven stats (strength, willpower, comprehension, courage, bone structure, spirituality, appearance) hand-allocated from a shared 160-point pool, with an eighth stat, "fortune," auto-filled from whatever is left. Beyond the newbie "Snow Village" zone (pawnshops, inns, a pharmacy) the map extends into a "bygone era" region (a smoke pavilion, hillside paths, a flagstone official road) outside the main storyline. Its rank system adds a tier above the usual top rank — "(manager)" outranks "(admin)" and is held by the archive's two built-in admin accounts. A diff against this project's kxkj (#036, "Rhapsody Realm") shows byte-identical master.lpc, securityd.lpc, and death-system files — not an adaptation or sequel, but another independent snapshot of the same 2002 release by Taiwanese wizard wade, differing by only about 10 runtime files. Each archive independently built its own newbie zone under the shared name "Snow Village," with different actual content — evidence of two separately built instances of the same engine source rather than one being derived from the other.

README

内容亮点

注册流程

英文名字(3-10 个小写英文字母)→ 确认建立新角色(y/n)→ 中文名字 (1-6 个中文字)→ 密码(至少 5 位)→ 确认密码 → 电子邮件地址 → 性别(m/f)→ 互动式天赋分配(膂力→定力→悟性→胆识→根骨→灵性→容 貌,共 7 项手动输入,总点数池 160 点,每项合法范围 10-30;第 8 项 「福缘」由剩余点数自动填入,不再单独询问)→ 按 Enter 键继续 → 进 入游戏世界。

本次修复的关键 bug

1. 358 个文件用了尖括号绝对路径 #include(例如 #include </open/open.h>)。本驱动把尖括号 include 解释成「相 对于 include 目录」而不是「相对于 mudlib 根目录」,这些绝对路径 一个都解析不了。文件数量太多,不适合逐一手改,改用 AGENTS.md §6.1 记载的批量解法:在 master.lpc 加一个 include_file(compiled, from, path) apply,对以 / 开头的 路径前面再多加一个 /,逼driver走跟带引号 include 一样的 merge() 路径解析逻辑——一次修好,不用碰任何一个原始文件。 2. adm/daemons/natured.lpccheck_heart_beat() 里有一个 多打的分号,让一个 if 判断式提前结束,变成一句空语句,导致后 面那句原本应该被这个 if 保护的 "/adm/daemons/temp.lpc"->record_heart_beat(...) 变成每秒都会 无条件执行一次——而这个 /adm/daemons/temp.lpc 档案在整个档案 里根本不存在(应该是开发时期的调试用 daemon,发布时被拿掉但呼 叫点没有清干净)。确认过整个仓库都没有这个档案后,直接移除了两 处呼叫,而不是凭空补一个档案出来。

管理员账号 / Admin account

这个 lib 的权限体系里 (manager) 是比 (admin) 更高的一级,管理 员名单里原本两个条目(chenwun)都是 (manager),所以新增的 fluffos 账号也用了同一级别,存储在纯文本文件 adm/etc/wizlist 里;账号本身通过正常注册流程创建,已在游戏内确认 "目前权限 -> (manager)" 显示正确。

此前的账号记录曾标注密码为 Mud2026Adm,但存档核实(§10.7 深度
测试)发现 adm/etc/wizlist 虽有 fluffos (manager) 一行,
data/login/f/ 下却从未真正生成过 fluffos.o 存档——权限名单
曾被单独播种,账号本身从未走过注册流程。已用标准 fluffos/
Mud@2026 组合重新完整注册,标准密码本身可正常使用。另注意:本
档案的账号存档只在真正的 socket 断线(非 quit 指令)时才落
盘,用 kill 结束驱动进程前务必先正常断线,否则未落盘的账号会
"凭空消失"。
警告:这是一个公开的默认密码,仅供本地/浏览器试玩。正式对外开服前
请务必修改此密码。

本地运行

cd libs/kxkjii2
~/src/fluffos/build-debug/src/driver config.fluffos

游戏端口:40160

NOTES · 移植与修复记录

WASM 修复摘要(迁移自 meta.json 的 group_note)

ES II/Annihilator 衍生的 mudlib(狂想空间II,"Wild Imagination Space II"),和 jyqxc 家族是不同的血统(存档用驱动原生的明文格式,不是那种旧式二进制编码)。修复了两个 bug:(1)358 个档案用了 AGENTS.md §6.1 类的尖括号绝对路径 #include(例如 #include </open/open.h>),这个驱动会把它解析成相对于配置好的 include 目录而不是 mudlib 根目录——因为分散得太多,不适合逐一手改,改用了文档记载的批量解法:在 master.lpc 里加一个 include_file(compiled, from, path) apply,给看起来是绝对路径的 <> include 前面多加一个"/",逼它走和带引号 include 相同的、基于 merge() 的路径解析。(2)adm/daemons/natured.lpc 的 check_heart_beat() 里有一个多余的分号提前终止了一个 if 条件(if (wizardp(...) && ...);),导致后面那句原本应该被这个 if 保护的、对不存在的精灵 /adm/daemons/temp.lpc 的 call_other() 变成每次心跳(每秒,永远)都会无条件执行——确认过这个档案在整个代码库里从来不存在(一个被剪掉的开发/调试用暂存精灵,两处呼叫点从没清理干净);已直接移除这两处死呼叫点,而不是凭空捏造一个替代精灵。注册流程用的是一套特别的互动式点数分配(7 项明确询问的属性、从共享的 160 点数池里分配,第 8 项由剩余点数自动填入),不是 jyqxc 家族那种"自动生成后接受"的模式。这里的管理员等级惯例是 (manager),比 (admin) 高一级——已把 fluffos 播种为 (manager),匹配既有的两个条目。完整的注册→look→score→quit 流程和管理员流程在排版格式化前后都验证过;格式化工具还原了 6 个真正损坏的档案(中文字符密集的字符串字面量里出现了错误的"\ n"分词——这是一种 jyqxc 家族里没见过的新损坏形态,和那个家族的 ASCII 地图压缩模式不同)。有几个 diff 特别大的档案(删掉了几千行)经确认是格式化工具正确地清理掉了几千行原本就存在的尾随空行,不是损坏。

§7.86 跨库扫描修复(留言板 post 崩溃)

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

血统确认:与 kxkj 同一份代码,非"改编"关系

kxkjii2(本档案,089 号)与 kxkj(036 号,本 session 早前已完成 §10.7 深度测试,结论"deep functional test,no programming bugs found")的 adm/obj/master.lpc 主体、adm/daemons/securityd.lpc 逐字节完全相同(diff 输出为空,除去两边各自独立打的 WASM 阶段 #include/compile_error 补丁);work/ 目录树结构、open/death/ 整个死亡区(start.lpcbridge1-3.lpcgate.lpcgateway.lpcroad1-3.lpc 等)逐文件字节完全相同;档案总数仅差 10 个文件(均为 运行期存档差异,非源码)。结论:这不是"狂想空间"改编或衍生的 "II"续作,而是同一份 2002 年台湾巫师 wade 发布源码的另一份独立快照/ 存档,与本档案自己 README 已有的"和 dfgsiiv13b 的雪村同名但内容不 同——应是各自独立搭建"判断相印证(雪村内容确实独立,但引擎/核心代码 是同一份)。这次比对方式:直接 diff 两份 work/ 下的核心精灵文 件与整个 open/death/ 目录,而非仅凭档案名或简介推测。

管理员账号:README 记录与实际存档不符,已重新播种

README 此前记录的 fluffos/Mud2026Adm/(manager) 虽然 adm/etc/ wizlist 里确实有一行 fluffos (manager),但 work/data/{login, user}/f/ 下翻遍都没有对应的 fluffos.o 存档——说明上一轮只写了权 限名单,从未真正走过注册流程创建账号。本次用标准 fluffos/ Mud@2026 组合重新走了一遍正常注册流程(英文 id → y 确认 → 中文名 "浮浮" → 密码×2 → 邮箱 → 性别 → 7 项天赋各分配 20 点,第 8 项福缘 自动补满),目前权限 -> 【天帝】(manager) 立即生效。额外发现并 确认的存档时机细节:本档案的 LOGIN 对象(含密码的账号存档)既 不在 quit 指令路径写盘,也不在注册完成时写盘,只有真正的 socket 断线(net_dead())才会写——这点与 kxkj 已有 NOTES 记录的现象一 致。第一次用 kill <driver pid> 结束驱动进程时验证到这一点:重启 后 data/login/f/ 目录为空,fluffos 账号"凭空消失",被迫重新注 册;第二次改用正常 telnet 断线(结束 tmux 会话,触发真实 TCP FIN/RST)后确认 data/login/f/fluffos.o 正确落盘,且用标准密码 Mud@2026 成功重新连线("重新连线完毕。"),(manager) 权限保留。 密码沿用项目标准 Mud@2026,未被拒绝,无需变体密码。

核心发现并已修复:死亡后唯一的"复活"出口是死代码,此前每一次死亡都是无法恢复的死局

用 (manager) 账号在 d/snow/eroad2(雪村,与 kxkj 完全同一房间) 对 野狗(非会说话生物,走真杀分支)使用 fight dog,走完完整的 真实死亡流程("你死了" → 送往 阴曹入口),debug.log 全程干净, 无任何报错——死亡本身完全正常,问题出在复活出口。

open/death/start.lpc阴曹入口)的 exits 映射只有 "north" 一个活 key,"up"/"south" 两行被注释掉;但同一文件的 valid_leave() 紧接着就有这两个方向完整可用的逻辑("south" 是 "你真的想回家吗"的 5-9 次随机重试门槛,"up" 才是真正的复活: reincarnate() + move() 回到人间,打印"恭禧你又重回人世了")。 本档案共用的 cmds/std/go.lpc 在呼叫 valid_leave() 之前先检查方 向是否是 exits 映射里的一个 key(undefinedp(exit[arg]) 直接 return 0),所以 "south"/"up" 这两条分支从来没有被真正执行 过——玩家打 south/up 得到的是"什么? south? 请用 help cmds 查询 指令。",和真正不存在的指令一模一样。已在 open/death/start.lpc 里把这两行注释还原(详见 AGENTS.md §7.101,新增条目)。已在 kxkj 上核对同一档案,确认逐字节相同、同样命中此 bug、本次未修 (留给下次触碰 kxkj 时同步)。

现场验证(修复前 → 修复后对照): 1. 修复前:阴曹入口look 只列出 north 一个出口;打 south/up/stand 全部得到"什么? xxx? 请用 help cmds 查询 指令。"。沿仅有的 north 出口一路走到底(bridge1→bridge2→ bridge3→鬼门关→酆都城门→鬼门大道),发现这整条支线本身也是 未完成内容:open/death/road3.lpc 的房间描述原文是"路的尽头 ..... 还没想到 ...."(原作者自己留下的占位文字),且 open/death/gate.lpc 一旦向北经过就永久拒绝向南返回("进了鬼 门关就别想回去了!")——这条支线一旦被误闯,除了管理员 goto/transfersuicide -n/-f(自毁角色)之外没有任何 出路。鉴于这条支线是原作者明显未完成的探索型旁支内容(非死亡 恢复的必经之路),本次判定为超出"程序 bug"修复范围的既有设计 /未完成内容,只记录不改动。 2. 用 update /open/death/start 热重载后,look 显示三个出口 upnorthsouth;连续 south 6 次后出现"你真的那么想回 家吗? 好吧!!就如你所愿"门槛消息;up 立即触发完整复活: "突然天中降下一团祥光,令人无法用肉眼直视。过了一会儿,祥光散 去,一道人影出现,你终于从阴间偷跑回来了。",落地新手入门房, score 确认属性/气血/食物饮水全部正常。重启驱动(完整冷启动而 非仅 update)后二次复验,行为一致。

与 §7.68/§7.76 的区别(明确排除,非同一形状):本档案确实也有 经典的 wgargoyle.lpc/bgargoyle.lpc(白无常/黑无常) present(ob) 门控 call_out("death_stage", ...) 五阶段对话链(位 于 鬼门关/酆都城门),且 wgargoyle.lpcinit() 正确地对 wizardp(previous_object()) 做了排除(管理员角色路过不会触发这条 链,符合本项目已确认的既有惯例,未见异常)。但本条修复的根因与这 条对话链完全无关——是 阴曹入口(死亡后第一站)的移动指令被共用 派工器在方向未列入 exits 前直接拒绝,valid_leave() 根本没有机 会执行,不是 present() 检查落空或链条被中断。

已知 bug 类别逐项核对(除上述新发现外)

额外顺手修复:master.lpc 把编译警告当错误广播给玩家(对齐 kxkj 已有修法)

kxkj(同代码血统)的 master.lpc 早前已经把 log_error() 里的 efun::write("编译时段错误:" + message) 改成只在 message 不含 "warning:" 时才广播;kxkjii2 这份存档没有这个过滤,导致注册/ 装备/进房间等任何触发懒编译的操作都会把内部档名、行号、"Unused local variable"级别的无害警告原样刷给正在连线的普通玩家。已在 kxkjii2 自己的 master.lpc 里补上同样的过滤(保留写日志,只是不 再广播警告级别的消息)。现场验证:重启驱动后重新注册一次 fluffos,整个注册流程不再出现任何"编译时段错误:"前缀的警告刷 屏,功能行为不变(真正的编译错误仍会照常广播,未测试到真错误场 景,逻辑与 kxkj 完全对齐)。

测试覆盖小结

注册(含账号丢失重建、密码变更核实)→ look/scoreenter 正 式进世界 → 移动(新手房→狂想空间入口处→中央驿站→雪村黄土小径,管 理员 goto 快速定位)→ 留言板 post/look board → 战斗 (fight dog,真实杀伤分支)→ 完整死亡 → 复活出口 bug 定位与修复 → 修复后完整复活链路复验(含冷启动后二次复验)→ 清理测试残留 (death_record 测试行、data/board/start_b.o 测试留言、 data/clan.o 的例行序列化 mtime 变动均已用 git checkout 还原, 不纳入本次提交)→ 正常 telnet 断线以确保账号存档落盘。

§7.100 跨库扫描修复(ROOM 基类同款 replace_program() 致命形状)

``§7.112`` residual-gap closure (2026-08-20)

Corpus re-scan (grep -rl 'call_out("death_stage"' ... | filter for missing guard) found unguarded init()-scheduled death_stage() call_out chain(s) in open/death/npc/bgargoyle.lpc, open/death/npc/wgargoyle.lpc that the original two-wave sweep (see AGENTS.md §7.112) missed -- same reconnect-triggered duplicate-chain bug, different filename/lineage. Added the standard query_temp("death_stage_active")/set_temp/delete_temp re-entry guard, adapted per file's own exit points. Compile-verified via lpcc --batch.

§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.

Ported §10.7 go.lpc/clan fixes from sibling kxkj (2026-08-21)

cmds/std/go.lpc, cmds/adm/update_dir.lpc, cmds/imm/{ls,lstree}.lpc, cmds/clan/c_create.lpc, and adm/daemons/cland.lpc all confirmed byte-identical (md5sum) to kxkj's pre-fix copies. kxkj's round-four §10.7 deep test found 4 real bugs:

1. go.lpc's .lpc-suffix check sliced only the last 2 characters against the 4-character literal ".lpc", so it never matched and always appended .lpc -- including to paths that already had it. Since the exits mapping is held by reference, this permanently and cumulatively corrupted any room's exit whose destination lacked a literal .lpc suffix in source, breaking that exit for every player after the first use until reboot -- a severe navigation-corrupting bug. Same copy/paste bug in update_dir.lpc (made that admin recompile tool a total no-op) and cosmetically in ls.lpc/lstree.lpc (wrong file-type coloring). 2. c_create.lpc called CLANV_D->create_clanv(), which doesn't exist on the live daemon -- the silent undefined-function no-op meant a clan founder's own clan/name property was never set. 3. cland.lpc's clan_query() lacked the undefinedp() guard its sibling clan_set() has, crashing c_list with "Value being indexed is zero" under a specific directory/data-entry mismatch.

Ported all fixes directly from kxkj's fixed files rather than re-deriving them. Compile-verified via lpcc --batch (not independently live-boot-tested on this lib -- the fixes were already proven live on kxkj).

§10.7 shop purchase + sect apprenticeship (2026-08-24) -- both clean, no bugs found

Two gaps flagged as never tested in this lib's history: buy at a SHOP-type store, and apprentice (拜师) at an NPC sect master. Live-boot tested with the (manager) fluffos account (real telnet disconnect afterward, save residue reverted via git checkout -- this run only touched money/family/inventory scratch state, no persistent account change was intended).

Shop purchase (/open/center/room/store.lpc, 中央驿站商店): this lib's only SHOP-type stock (std/room/shop.lpc) starts genuinely empty (sells = ([])) -- there's no seeded starting inventory anywhere in the two SHOP-inheriting rooms found (store.lpc, open/magic-manor/town/town20.lpc); stock is populated entirely by players sell-ing items to the shop first, which the shop then resells. This is a real design (matches do_list's own "在这你可以 买到" empty-until-stocked framing), not a bug. Tested the full cycle with a cloned /open/main/obj/sword.lpc (base value 400): sell sword -> correctly priced at trade-skill-adjusted (int)(400/400* (300+trade_lv)) = 301 (三银一文钱), item appears in list priced at face value 400 (四两银子) as expected. buy sword first correctly failed ("你身上没有足够的钱币买东西") when the player's cash (301) was less than the buy-side price v/100*(230-trade_lv) = 916; after topping up with a cloned gold coin (10301 total), buy sword succeeded, charged exactly 916 (九银十六文钱), left correct change (9385 = 九十三两银子 + 八十五文钱), moved the sword into inventory, and removed the now-fully-bought-out entry from list/sells. All change/inventory math checked out exactly; no crash, no double-spend, no stock-count mismatch.

Sect apprenticeship (apprentice / cmds/std/apprentice.lpc + feature/apprentice.lpc): checked this lib's apprentice.lpc against the AGENTS.md §7.117 "did you betray your old sect" pattern (unguarded family_name string comparison rejecting first-time applicants with no family yet) before testing live -- this file does not have that shape. Its only prior-membership check is if (me->query("family")) { ... "决定背叛师门" ... } (a plain existence check, correctly falsy for a fresh character with no family set yet), so it was never a §7.117 candidate and needed no fix. Live test: joined the fighter class (join, near open/snow/npc/master_liu.lpc/雪苍派 in 雪苍紫云宝殿, /open/snow/room/room2), then apprentice master liu -> NPC's attempt_apprentice() checked class match (fighter) and str >= 20 (both satisfied by a fresh character's defaults), immediately recruit-ed the player with no rejection or betrayal-check false positive. score afterward correctly showed 雪苍派第五代弟子 (5th-generation disciple) and 师父: 刘全书 (master: Liu Quanshu) -- family/title assignment and generation-number formatting all correct.

No programming bugs found in either area; both are clean. ~90 tool calls for this session (boot, both playthroughs, code review, cleanup).

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), and byte-near-identical to the sibling fix on kxkj: 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()/reset_me() chain -- confirmed via a body-aware static scan of every init() in this lib: 6 NPC files call setup()/reset_me() from init() (e.g. d/snow/npc/waiter.lpc and the open/gblade/npc/pker*.lpc family), after create() 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, re-entering the same chain while the original call is still on the stack -- genuine reentrancy, crashing with "Too deep recursion" (most likely on an NPC's first-ever preload/compile).

feature/damage.lpc's revive() calls enable_player() again while the object is still living(). This confirms a bare if (living(this_object())) return; guard would be the WRONG fix -- 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 the legitimate revive re-enable unaffected. enable_player() had a pre-existing early return, if (!this_object()) return;, right at the top -- added in_enable_player_now = 0; before that return too (rewritten as a 2-line block) alongside the fall-through exit at the end. Verified via a single-file lpcc --batch compile check (PASS) -- not individually live-boot-tested.