Mire I (nt1)

✅ 可玩

泥潭1

nt1

🔑 fluffos / Mud@2026 更新 92ed283 2026-09-04 源码 下载 ZIP

▶ 开始游玩 · Play Now

NT/nitan("泥潭"/Lonely)系代码线的独立分支,和 nitan170911/nitan6/ hhsj 都没有精确的 master-hash 匹配。新角色要先进入"注册房间",用房间内置的 `register <email>` 指令完成邮箱登记才能正式进入游戏世界分配天赋属性——和"地狱"/Doing 家族的"投胎"机制类似,都比常见的 "选完性别直接进游戏"多一道关卡;角色创建除性别外,还要在独立的性格类型(1-4)和角色类型(1-5)之间各做一次选择。

English

An independent branch of the NT/nitan ('Mire') engine family, with no exact code match to sibling games nitan170911, nitan6, or hhsj — it grew up on its own. New characters must first pass through a 'registration room' and use a built-in register-with-email command before their attributes are even assigned, an extra gate reminiscent of the 'reincarnation' step found in the Hell/Doing family of games. Character creation also layers a temperament type and a character-class type on top of the usual gender choice.

README

内容亮点

注册流程

new 触发注册 → 字符集选择(gb/big5,任何非 big5 的输入都会被 当作英文 id 直接尝试,所以 GB 用户可以直接跳过这一步输入 id)→ 英文 id(3-10 个英文字母)→ 确认创建(y/n)→ 中文姓氏(可留空,不超过两 汉字)→ 中文名字(不超过两汉字,姓名合计至少 2 个汉字)→ 管理密码 (至少 5 个字元,用于密码找回/巫师密码)→ 确认管理密码 → 普通密码 (至少 3 个字元,且不能与管理密码相同)→ 确认普通密码 → 性格类型 (1-4)→ 角色类型(1-5)→ 性别(m/f)。

新角色会先进入"注册房间",需要用房间内置指令 register <email> 完 成邮箱注册后才能正式进入游戏世界分配天赋属性。

本次修复的关键 bug

管理员账号 / Admin account

由于 wiz_statusnosave 的,管理员是通过在 adm/daemons/securityd.lpccreate() 里硬编码 wiz_status["fluffos"] = "(admin)"; 实现的,每次驱动重启都会重新生 效(无需编辑存档文件)。

警告:这是一个公开的默认密码,仅供本地/浏览器试玩。正式对外开服前
请务必修改此密码。

在线试玩

https://mudlibs.fluffos.info/nt1/

本地运行

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

游戏端口:40185

NOTES · 移植与修复记录

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

NT/nitan 血统,和 nitan170911/nitan6/hhsj 没有精确的 master 哈希匹配;修复了一个真实存在的内容 bug(clone/user/user.lpc 的 accept_kill() 呼叫 is_killing(ob) 而不是 is_killing(ob->query("id"))——这份代码库里其它所有地方 is_killing() 都是接受字符串 id 的);修复了 efun::message() 对 exc_target=0 的拒绝;启动干净。完整 WASM 修复:给 band.lpc 的 is_banned() 加了本地回环放行(这条血统会禁掉任何不符合严格四段式点分格式的地址,否则连 ::1/非点分地址也会被误禁);修复了 is_chinese() 过时的 GBK 字节区间判断;修复了 check_legal_name() 过时的字节长度下限,以及 named.lpc 的 invalid_new_name() 里一个独立的、假设"4 字节=2 字符"的 bug(会把任何单字给名当成"空名字"拒绝,滑窗查重逻辑也是按原始字节偏移而不是字符步进);logind.lpc(create()/make_body())里同样的 seteuid(getuid()) 把 euid 重置掉的 bug。最大的一个:adm/daemons/versiond.lpc(约 2300 行,一个绑了 TCP 服务端/客户端的版本同步/校验和精灵)在 13 个各自独立的函式里无条件呼叫 socket_create/socket_bind/socket_listen/socket_accept/socket_address/socket_write/socket_close,导致整个档案编译失败——而 logind.lpc 的 logon() 会在每一次连线时无条件呼叫 VERSION_D->is_version_ok(),所以这个编译失败会静默破坏每一个玩家(不只是巫师)的登录。按 AGENTS.md §7.52 更新后的方针,只把涉及 socket 的函式掏空成 no-op(不是整个档案,因为 is_release_server()/append_sn()/query() 等函式被这份 mudlib 里其它几十个无关的 NPC 和指令呼叫)——这就是连线在开机横幅之后永久静默挂起、没有提示也没有错误堆栈的根本原因。另外发现并修复了从原始压缩包继承下来的、真正损坏的非 UTF8 目录/文件名(d/heimuya、d/jingzhou、d/tulong、d/beijing/map 下共 20 个条目,外加一份多余的 work/version/ 备份副本里的重复条目),这些名字会让基于 Node 的 WASM 测试工具目录扫描直接崩溃(scandir 报 ENOENT)——已改名为安全的 ASCII 文件名,内容不变。管理员账号通过 securityd.lpc 的 create() 里代码层面的默认值播种(这里的 wiz_status 声明为 nosave——从不持久化到存档档案,所以没有数据档案可编辑,播种只能写在代码里)——另外还得 mkdir -p 一个缺失的 log/nosave/ 目录,它会在播种那行代码执行之前就让 create() 静默中止(restore() 自己的 log_file() 呼叫抛出未捕获的异常,只被 master.lpc 最外层的 preload() catch() 接住)。新角色会落在 /d/register/regroom.lpc,需要用游戏内的 register <email> 房间动作才能继续进入真正的游戏世界——这个加上每秒一次的心跳驱动时钟重绘提示,让脚本化的注册后指令验证在 WASM 测试工具里不太可靠(和 nt6/nt6nitan6win 已记录过的同一类无害计时 artifact);核心的注册+进入游戏世界+管理员权限识别路径已经过扎实验证,不受影响。

深度功能测试(§10.7,本轮)

之前几轮只做到 WASM 修复+注册流程验证这一层。这次用真实驱动起服,用 scripts/mudclient.py(单一连续 session)完整走了两次注册流程(不同中文名字),全程检查 debug.log

发现并修复的 bug

1. 与 xfbhh/hhsj 同宗的 §7.78,但这是一条独立血统(和它们没有精确 master 哈希匹配)——证明这不是同一份代码复制出来的巧合,而是同一个上游 NT/nitan 架构本身的通病。 inherit/char/char.lpc 组合出 CHARACTER 用的 mixin 文件(alias/apprentice/attack/attribute/command/damage/message/more/name,这份代码库用 F_SHELL 替换了 xfbhh/hhsj 的 F_EQUIP_LIV,同样是独立分支的证据)里有裸 set()/query()/query_temp()/delete() 调用,这些文件自己都没有 inherit F_DBASE。数量比 xfbhh/hhsj 少很多(这份代码大部分地方已经在用 ob->set()/query() 这种正确的写法),但 command.lpcenable_player() 依然是同款 set_living_name(query("id")) 崩溃写法——每个新角色登录都会触发。修法一致:改成 this_object()->set(...)/this_object()->query(...)

2. 新发现:adm/daemons/eventd.lpc 的文件名截断算术错误,让每一个区域事件永久失效。 event_name = map_array(event_name, (: $1[0..<3] :)); 想要去掉 .lpc 后缀(4 个字符),但这个驱动的 str[0..<n] 语义是"保留 len-n+1 个字符",[0..<3] 实际只去掉了 3 个字符,留下一个尾随的 . 加上后缀的第一个字母——"emei.lpc" 变成了 "emei.l" 而不是 "emei"collect_all_event() 于是对着一个根本不存在的路径(/adm/daemons/event/emei.l)做 call_other,5 个事件档案(emei/huanggs/qiantang/tianchi/wuliang)全部中招,每次开机都会静默报错、区域事件系统形同虚设。改成 [0..<5](保留 8-5+1=4 个字符,正好去掉 .lpc)。

3. 新发现:inherit/misc/quest.lpcset_information() 参数类型收窄,导致 9 个任务档案全部编译失败。 void set_information(string key, string info)info 声明成 string,但它转发给的 adm/daemons/questd.lpc 自己的 set_information(object qob, string key, mixed info) 早就正确地用 mixedclone/quest/{capture,shen,supply,trace,search,explore,deliver,judge}.lpc(以及一份 defend.bak,未编译,不算在内)全都传一个函式指标 (: ask_npcN :) 作为 info,被这个过窄的中间层签名挡下来,变成 "Bad type for argument 2... string vs function" 编译错误——9 种任务类型全部因为一个共同祖先档案里的一行代码而完全无法使用。改成 mixed info 后一次性修好全部 9 个档案,不需要动任何一个任务内容档案本身。这也是 AGENTS.md 里提到过的教训的又一个实例:一个编译错误在一堆看起来无关的档案上以相同形式反复出现时,先查它们是不是共享同一个继承祖先。

未继续测试的部分

时间关系,两次测试都验证到"完整注册+进入注册房间+debug.log 干净"为止,没有走到 register <email> 之后的先天属性分配(南贤处 ask nan about gift)、移动、战斗、任务实际触发等更深的内容——不过第 3 点的编译错误修复本身已经确认了 9 个任务档案现在至少能正常编译加载,比修复前的"完全无法使用"是质的提升。下一轮如果继续深挖这个 lib,建议从 ask nan about gift 开始,走完先天属性分配,再实际触发一个任务验证 set_information() 修复后的运行时行为。

深度功能测试第二轮 / Deep functional test round two (2026-08-15, post driver-upgrade re-test)

驱动于 2026-08-12 升级(引入 PR #1343/#1344)后的重测。复核第一轮结论 均仍成立(§7.78 mixin 修法、eventd.lpc[0..<5] 修法、 quest.lpcmixed info 修法逐一 grep 确认仍在)。标准检查清单发 现并修复三处问题,其中一处是本项目迄今在这条 NT/nitan 血统上找到的 最严重的一类 bug:一个会让驱动长时间(观察到持续 11 小时以上) 几乎满载单核 CPU、并不断悄悄重写数百个真实玩家存档的未捕获异常。

标准检查清单发现的修复

1. config.fluffosmaximum evaluation cost700000(已知 风险区间)提升到 5000000(本项目标准安全值)。 2. adm/simul_efun/file.lpclog_file() 原本直接 write_file(LOG_DIR + file, text),没有 assure_file() 目录预建 保护;补上调用,并加了前向声明(log_file() 在文本位置上先于 assure_file() 自身定义——本驱动不容忍未声明的前向调用,否则整 个开机会崩,参照本 session 在 dtsl/mhxy 上的发现)。 3. AGENTS.md §7.10(log_error() 警告泄漏)——一次自我纠正的案 例adm/single/master.lpclog_error() 原本对 this_player(1)/this_player() 无条件广播所有编译诊断消息,包括 纯警告。第一次尝试用 strsrch(message, "warning:") 修复(小写), 现场登录测试时立刻发现漏了——本驱动实际吐出的是大写 Warning: Unknown #pragma, ignored.,逐字符不匹配。查阅 AGENTS.md §7.10 才 发现这正是文档里明确记录过的坑:"警告"大小写在这个驱动的历史上 反复横跳过,正确写法是匹配不带开头 w/W 的子串 "arning:"(在 "warning:"/"Warning:" 两种大小写下都能命中)。改用这个写法后 重新开机验证,update/look 等操作不再往玩家屏幕泄漏警告文本。

严重 bug:adm/daemons/closed.lpcload_all_users() 未捕获

restore() 异常,导致数小时级 CPU 满载和真实存档的静默重复重写

现场发现过程:第二次开机(已应用上面三处修复)后尝试登录,密码 验证卡住数秒才有响应;ps 显示驱动进程持续 80%+ CPU。经过一次长达 数小时的环境挂起(sandboxed 环境两次工具调用之间被暂停了相当长时 间——这不是我主动等待造成的,但恰好让 bug 有足够时间充分暴露)后 再检查,驱动进程本身已经消失(非我手动 kill),log/(准确说是驱 动 stdout 重定向文件)里有超过 20000 行输出,其中 127 处 Eval interrupted...cost limit reached, limit: 5000000 usec 错误, 横跨 clone/user/user#707#5085 共数千个不同的对象编号;git status 显示 data/user/*/*.odata/login/*/*.o 下约 200 个真实 玩家存档全部被标记为已修改。

根因adm/daemons/closed.lpc 是"闭关修炼"(离线自动修行)子系 统的心跳精灵,heart_beat() 每 10-19 秒调用一次 load_all_users(), 对 closed_users 映射里记录的每一个"正在闭关"的账号 id,尝试 new(LOGIN_OB)login_ob->restore()LOGIN_D->make_body()user_ob->restore()LOGIN_D->enter_world() 完整走一遍真实登录 流程,把该玩家临时加载为一个真正的、活的 clone/user/user 对象。 本档案原始归档自带的 data/closed.o 快照里 closed_users 记录了 约 150 个账号正处于闭关状态——这是原始存档内容本身的特征(真 实玩家在归档快照那一刻确实处于这个游戏状态),不是转档或本轮测试 引入的问题。

问题出在 feature/save.lpc:18restore()

int restore() {
  string file;
  if (stringp(file = this_object()->query_save_file()))
    return restore_object(file);
  return 0;
}

这里直接调用 restore_object(file),没有 catch() 包裹。当 closed_users 里某个账号的存档本身有真实数据损坏时(现场复现: clone/user/user#707/#716*restore_object(): Illegal mapping format while restoring alias.——alias 这个属性的映射格式损坏, 且这是原始归档数据本身的损坏,与本轮任何修改无关),restore_object() 抛出的运行时错误会一路未捕获地穿透 clone/user/user.lpc::restore()closed.lpc::load_all_users()closed.lpc::heart_beat(),导致: 1. 当次 heart_beat() tick 提前中止,closed_users 里排在这个损坏 账号之后的其余条目该 tick 完全没被处理到; 2. 更严重的是,load_all_users() 里原本设计好的清理逻辑 (destruct(login_ob) + map_delete(closed_users, u))因为异常 直接跳过了函式剩余部分,永远执行不到——这个损坏账号既没有被真正 清理掉,也没有从 closed_users 里移除,于是下一次 heart_beat tick 会用一个全新的对象编号重新尝试加载同一个账号,重复触发同一 个异常,如此往复,永不收敛。 3. 与此同时,closed_users 里其余 ~150 个没有数据损坏的账号, 每个完整登录流程本身也相当昂贵(make_body()/enter_world() 涉及大量装备/技能物件的首次编译),部分账号单独就能吃满 maximum evaluation cost(哪怕已经提到 5000000usec=5 秒)——这 是本次观察到的另一层性能问题,load_all_users() 每个心跳周期都 要完整重新走一遍所有 ~150 个账号的登录流程,而不是加载一次后长 期复用,导致驱动实质上要持续模拟 150 个"幽灵在线玩家"的完整心跳 负载,这个规模在这台测试硬件上足以让单线程驱动长期维持 80-95% CPU 占用。这一部分是"设计上开销很大但架构本身工作正常"(原始 enter_world() 失败路径本身已经有 catch() + 清理逻辑,第 153-158 行),不是本节要修的未捕获异常类别,留作已知的高开销特征 记录,不在本轮改动范围内(不是程序性 bug,属于"离线闭关模拟"这 个游戏机制在真实存档规模下的正常固有成本,触碰这部分需要重新设 计整个心跳节流策略,超出本轮"修复真正的程序 bug"范畴)。

修复:只处理未捕获异常这一层(真正的程序 bug),给 load_all_users() 里两处 restore() 调用分别包一层 catch()

// 修复前
if (!login_ob->restore()) { ... }
// 修复后
err = catch(ok = login_ob->restore());
if (err || !ok) { ... }

user_ob->restore() 同样处理。)修复后重新开机验证:同一个损坏账 号(clone/user/user#716)现在被正确捕获并记录为"错误讯息被拦截", 而不是让整个 tick 崩溃;观察 150 秒的开机+运行窗口,Eval interrupted 错误数保持为 0(此前第一次开机在类似时间窗口内已经出 现超过百次)。

现场登录验证的局限性说明(方法论坑,非程序 bug)

尝试验证真实管理员写入权限时遇到一个和账号持久化相关的方法论陷阱, 记录下来供以后同宗测试参考:本档案 cmds/usr/quit.lpcmud_age < 1800(账号创建不满 30 分钟)的新账号,quit 指令不会走 正常的 me->save() 路径,而是弹出"退出该游戏将删除你的账号,你确 定要放弃该帐号而退出吗?(y/n)"的确认——本轮第一次测试会话里,注册 完 fluffos 管理员账号后立即发送了 quit(账号显然远不满 30 分钟 新),随后为了应用 master.lpc 的修复而直接 kill 了驱动进程,没有 等待/回答这个确认提示,导致该次会话从未真正走到任何 save() 调用。 重启驱动后用同一账号 id 重连,密码验证失败(密码错误!)——这不 是密码持久化的程序 bug,而是这条 NT 血统里已经在 kxkj(ES II 血 统,见其 NOTES.md)独立记录过的同类设计:注册期间设置的密码只存 在内存里,真正落盘要么要等到 mud_age>=1800 后的正常 quit,要么 需要一次干净的 net-dead 断线,强行 kill 驱动会跳过这两条路径。本 轮改为依赖第一次会话内(未重启、账号仍在内存中)已经成功验证过的 update /adm/daemons/logind.lpc → "重新编译...成功!"作为管理员写 入权限的有效证据,未重新走一遍跨重启的账号验证。给未来同类测试的 提醒:测试新注册账号的跨重启持久化时,务必等待 mud_age>=1800 真实经过 30 分钟后再 quit,或者用断线(net-dead)方式结束会话, 不要在账号很新的时候强行终止驱动进程。

现场验证摘要

本轮修改的文件

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

§7.100 修复(ROOM 基类的同一形状 landmine,全档案扫描第 6 批)

``§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 d/death/npc/bai.lpc, d/death/npc/hei.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): 5 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.

深度功能测试(2026-09-04,shop + 拜师)

Live build-debug driver on port 40185. Prior rounds stopped at the register room (ask nan about gift / 先天 / 移动 / 战斗 deferred) or were static-only (§7.100 / §7.30). First send is gb (GB/BIG5 menu; gb is two letters so check_legal_id rejects it and re-prompts for the English id). No fluffos.o existed; securityd.lpc create() still seeds wiz_status["fluffos"] = "(admin)". New-character path: gbfluffosy → 秦 / 风 → admin password Mud@2026x → ordinary Mud@2026 → 性格 1 → 类型 5m. Lands in /d/wizard/wizard_room (WIZARD_ROOM overrides REGISTER_ROOM for non-players). Live clock prompt needs idle ≤0.45. register [email protected] in /d/register/regroom, then goto /d/register/entrywashborn 扬州人氏 (客店; help rules more-pager — send q). clone is is_admin() gated (VERSION_D->is_release_server() / admin_flag==21 / getuid()=="lonely") — design, not removed. giveall /clone/money/gold works via valid_grant("(admin)").

§7.110: first boot this pass kept data/closed.o (138 闭关 accounts). Port came up, but load_all_users() still mass-enter_world()s them; RSS climbed ~5.5GB in ~4 minutes and the 6GB ulimit -v process died. closed.lpc restore() is already catch-wrapped (round two); the remaining cost is the design of loading every closed account each heartbeat, not an uncaught exception. Subsequent boots in this pass moved closed.o aside for the shop/拜师 playthrough only (restored afterward, not committed). Never boot nt1 without a memory cap.

Bug 1 — goto aborted on MESSAGE_D->find_user(). cmds/wiz/goto.lpc always calls MESSAGE_D->find_user(arg) before treating arg as a path, which create()s /adm/daemons/network/messaged.lpc. startup_udp()socket_bind(socket_id, my_port) got string "10" (Expected: int Got: "10"). Uncaught error aborted the first goto (wizard stayed in 巫师休息室; the wizard register command then fired instead of the room action). Same shape as wxddym: LOCAL_PORT() is ((int) get_config(__MUD_PORT__)) but the bare (int) cast is a no-op on the string get_config() returns, and MESSAGE_PORT (10) arrived as string "10" too. Fixed create() to my_port = to_int(LOCAL_PORT()) + to_int(MESSAGE_PORT);. File is LF. After reboot, goto /d/register/regroom reached 注册房间.

Bug 2 — (admin) character creation dropped password / ad_password. feature/dbase.lpc set() refuses those keys when wizhood(id)=="(admin)" unless geteuid(this_player())==id. During input_to character creation this_player() is the login object, whose euid is not yet the new id, so ob->set("password", ...) in logind.lpc silently no-op'd. login.o saved without either secret; reconnect was 密码错误!. nitan3 had already commented this guard out; here it is kept but skipped when this_player()==ob (self during creation) so a third party still cannot set an admin password. File is LF. After the fix, login.o contains both hashes and relogin with Mud@2026 works.

Shop: /d/city/zuixianlou 店小二 (d/city/npc/xiaoer2.lpc). feature/dealer.lpc already #include <dbase.h>, so bare query("vendor_goods") resolves; list shows 烤鸡腿 80 文 without a this_object() patch. buy jitui deducted one gold into 99 两白银 + 20 文铜钱 and delivered 烤鸡腿. No F_DEALER 丐帮 refuse on do_buy (only do_pawn).

拜师: goto /d/city/lichunyuan, bai kong on 空空儿 (kungfu/class/gaibang/kongkong.lpc) — permit_recruit() only. Accepted: “恭喜您成为丐帮的第二十代弟子.” score shows 【门派】丐帮 / 【师承】空空儿. save (after the 30s last_save cooldown). After a full driver kill + reboot, relogin still shows 丐帮 / 空空儿; the silver/coin change persisted; 烤鸡腿 did not (food, not autoload — expected). Do not quit a fresh character: cmds/usr/quit.lpc offers to delete the account when mud_age < 1800.

Also fixed (same Huashan recruit-limit typo as nitan3): kungfu/class/huashan/{yue-buqun,yue-wife,feng-buping}.lpc recruit_apprentice() wrote add("apprentice_availavble", -1) (extra “av”) while attempt_apprentice()/reset() use "apprentice_available". CRLF preserved. qingcheng/yu.lpc is not in this tree.

debug.log for cd libs/nt1 && driver is libs/nt1/log/debug.log (opened before chdir into work/). Mudlib error_handler() also writes work/log/debug.log. Truncated both before the patched boot; Boot Time Fri Sep 4 05:55:42 2026 matches. No socket_bind / Error: lines on that boot. Demo fluffos save left uncommitted.