Twin Dragons of the Tang Dynasty (LLMUD)

✅ 可玩

大唐双龙传(LLMUD)

dtslmud

🔑 fluffos / Mud@2026 更新 66a3bd8 2026-10-28 源码 下载 ZIP

▶ 开始游玩 · Play Now

LLMUD v0.11 版,改编自黄易同名武侠小说《大唐双龙传》,故事背景设定在隋末唐初、群雄并起的乱世。经逐档比对,与本项目中的姊妹版本 "dtsl"共享同一血脉:约 99% 的场景档案逐字节相同,仅新手学院与 "石龙武馆"枢纽区经过重新编写。角色成长围绕膂力、悟性、根骨、身法四项基础天赋展开,注册时一次性分配(总和须为 80,每项 10-30);巫师/管理员的游戏内头衔在此版本中显示为"天帝"。深度测试实机验证了洛阳的花间派——一处经由候希白引荐、正确拒绝男性拜师的女子专属门派,海沙帮式"帮主之位可被挑战者以实战击败夺取"的帮派继承机制,以及经由扬州真实户外野外场景展开的完整杀人-死亡-复活流程,最终指向阎罗殿的鬼魂对话。

English

LLMUD v0.11, adapted from Huang Yi's novel "Twin Dragons of the Tang Dynasty" -- and, per a file-level check, running essentially the SAME 大唐双龙 authored game world as siblings dtsl/dtsl2: ~99% of its ~5,100-file domain tree is byte-identical to dtsl's, with only the newbie academy and 石龙武馆 hub area rewritten from scratch for this build (everything past it wasn't). Set at the fall of the Sui dynasty and the rise of Tang; four core attributes (膂力/悟性/根骨/身法) are assigned once at registration (must sum to 80, 10-30 each), and wizards/admins carry the in-game title 天帝 ("Heavenly Emperor") instead of the siblings' plain (admin) tag. Content beyond the shared world map includes a gender-gated, women-only sect (花间派, reachable in 洛阳 via 候希白) that correctly refuses a male apprentice, a 海沙帮-style gang-leadership system where headship literally changes hands to whoever defeats the incumbent in real combat, and a full kill-to-death-to-revival loop through 扬州's real outdoor wilderness ending at 阎罗殿's ghost dialogue.

README

内容亮点

在线试玩

https://mudlibs.fluffos.info/dtslmud/

管理员账号 / Admin account

警告:正式对外开放前请务必修改此密码。

本地运行

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

游戏端口:40015。

NOTES · 移植与修复记录

LLMUD(大唐双龙)v_0.11版.rar → dtslmud

Status: DONE — boots clean, playable over telnet

Booted with zero fixes needed (same lineage as dtsl, and the boot path itself was already fine there too — differences between these two snapshots showed up only in the lpcc sweep, not the boot path).

What was fixed (from the lpcc sweep, proactively applied since the

same two bugs were already known from dtsl's NOTES.md)

1. F_UNIQUE macro missing from include/globals.h despite feature/unique.lpc existing — same fix as dtsl, added #define F_UNIQUE "/feature/unique.lpc". Fixed ~101 files at once. 2. The same 6 clone/weapon/*.lpc files (jin-sjian, shizi, feihuangshi, jinhua, sjian, feidao) redundantly inherit COMBINED_ITEM; both directly and via inherit THROWING; — removed the redundant direct inherit, same as dtsl. 3. sm_ding.lpc (7th file in the same cluster) has the same unrelated, unfixed problem as dtsl: #include "/inherit/item/embed.h" doesn't exist anywhere in this archive either — genuine missing content, left as-is.

Routine conversion: encoding, .c→.lpc (6514 files, 1001 refs auto-fixed), 18 local angle-bracket includes converted to quotes automatically, static→nosave (85 files).

Known remaining issues (documented, not fixed)

How to run

cd libs/dtslmud
~/src/fluffos/build-debug/src/driver config.fluffos
python3 ../../scripts/mudclient.py 127.0.0.1 40015 --timeout 10 --send "" --send "look" --send "quit"

Post-hoc fix: UTF8-native is_chinese/registration (AGENTS.md §15h)

Applied in a later batch pass across the whole project: is_chinese/is_chinese2 in the shared chinese.lpc simul_efun fragment used GBK byte-range checks that silently never match real Chinese text once strings are UTF-8 (this driver's str[i] returns a Unicode codepoint, not a GBK byte). This broke character registration specifically -- any real Chinese name was rejected. Fixed the range check to test the CJK Unicode block instead, and halved the GBK-byte-calibrated length bounds in check_legal_name to match. See AGENTS.md §15h for the full writeup; confirmed via a real interactive registration test (Chinese surname + given name reaching the next prompt).

QA re-verification pass (2026-07-23) — found and fixed a real, high-impact bug

The earlier passes above never actually completed a full registration through to a live post-login look/score, so this genuine gap went undetected until this pass followed the project's standing policy of testing a real command after registration completes.

Bug found: every candidate starting room failed to load, silently stranding every new character in VOID instead of a real room (AGENTS.md §15s pattern). adm/simul_efun/message.lpc's tell_room(ob, str, exclude) (2-arg call form, ~99% of call sites) left exclude unset (0, an int, not an array) and passed it straight through to the real message() efun's 4th argument, which this driver rejects for a non-object/non-array value. Since tell_room() fires from feature/move.lpc's move() whenever any NPC's create() carries an item into itself (i.e. almost every populated room), this made every room containing an NPC with inventory throw an uncaught error out of its own create(), which the driver treats as a failed compile (Fail to load object ...). Confirmed directly via lpcc config.fluffos /d/yangzhou/zuihualou (the configured START_ROOM) failing with exactly this error before the fix, and compiling clean after.

Effect on a real registration: logind.lpc's enter_world() tries /d/newbie/door (the real new-player start room) first, and since it also has an NPC with inventory, IT failed to load too; the catch()-guarded fallback to START_ROOM then also failed to load for the identical reason, and neither failure's return value is checked, so the newly-created character was left with no environment at all. The very first look command's own !env safety-net (cmds/std/look.lpc) then silently dumped the player into /clone/misc/void ("最後乐园") instead — indistinguishable from a normal room without inspecting debug.log, since the void room has its own flavor description and exit.

Fix: adm/simul_efun/message.lpc, tell_room() — exclude || ({}) before the message() call; shout() — this_player() || ({}) likewise (same latent bug, same file, not yet observed live but same shape per AGENTS.md §15s's precedent on other libs).

Second fix, same pass: adm/obj/master.lpc's log_error() broadcast EVERY message reaching it — including ordinary compile *warnings*, not just real errors — to the connected non-wizard player as the alarming generic default error message ("你发现事情不大对了,但是又说不上来。"). Since the tell_room/shout bug above was firing constantly (any NPC anywhere with inventory), a real registration session before the fix showed dozens of these lines during ordinary preload/gameplay. Gated the broadcast on the message NOT containing "warning:" (AGENTS.md §15w's standard fix), still logging everything to file regardless. Verified: a post-fix session showed zero such lines.

Third, minor fix: d/gaoli/npc/xiake.lpc had a double-slash path typo (carry_object("//d/gaoli/obj/armor")) causing one *Read access denied. runtime error whenever this background NPC's job-daemon-driven room load fires while a player is connected — pre-existing content typo, unrelated to the two fixes above, fixed to the correct single-slash path.

Re-verified after all three fixes: full registration (id → y → 中文姓名 → password ×2 → email → gender → 4-attribute allocation → y) followed by look/score/quit, across 2 fresh characters (male "秦岭七"/female "秦岭八" — the female run also confirmed gender-specific starting clothes "粉红绸衫"/"绣花小鞋"). Both landed correctly in the real starting room (大唐学院/Tang Academy, /d/newbie/door), not VOID; look re-rendered the room, score showed the full correct character sheet; zero "你发现事情不大对了" lines and zero uncaught runtime errors in debug.log in the final run.

Re-verification pass: driver rebuild + LPC formatter + WASM build

WASM-enablement pass (loopback-allow / uptime / throttle / admin seed)

Standard pass per AGENTS.md §1.3(b)/(e), §1.5.

Gates patched:

Admin account: id fluffos, pw Mud@2026, name 浮浮, (admin) via adm/etc/wizlist. Registered via the real flow (id → y → 浮浮 → password ×2 → email → gender → "20 20 20 20" → yes). Verified: relogin shows 目前权限:(admin), update /adm/daemons/band → 成功, wizard fast-quit path ("作为巫师,你可以迅速离线"). Save files (untracked, NOT gitignored — orchestrator must add): work/data/user/f/fluffos.o, work/data/login/f/fluffos.o.

Retest: fresh normal registration (ceshisan / 秦岭辛) end-to-end OK (look/score/quit correct; test saves removed); no new errors in debug.log.

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

之前几轮只验证到"注册流程能走通+管理员权限确认"这一层,没有真正深入 玩过。这次用真实驱动起服,走了完整流程:注册(英文 id 3-8 字母、中文 名字、密码 5-8 字元、email、性别、"20 20 20 20" 分配属性)→ 进入"大唐 学院"新手村 → down 跳过新手教学 → 走到"石龙武馆"→ 逛兵器铺(list 正 确列出商品和价格,buy dagger from tie jiang 因为没钱被正确拒绝"你身 上带的钱不够啊!")→ 走到正厅试图攻击"武馆教头"(正确被"这里不准战 斗。"挡下)→ 试图直接离开武馆去外面世界(正确被"你经验这么低,出武 馆太危险了!"等级门槛挡下)。这些拒绝都是合理的游戏设计,不是 bug。 全程 debug.log 保持空白。

这份档案不是 NT/nitan 血统(虽然同样有 feature/dbase.lpc,但没有本地 定义 set()/query()——这是另一套架构,XKX 系),所以专门检查了一下, 确认不受 §7.78(mixin 文件裸 set/query 解析不到 F_DBASE)影响。

发现并修复的 bug

§7.68 死亡软锁的一个变体,d/death/gate.lpc。 和 xfbhh/hhsj/nt1 等 nitan 系那种"多阶段 death_stage() 自我重新 call_out"的写法不同,这 份档案的死亡流程更简单:init() 只排了一次 call_out("run", 5, ob),run() 本身完全没有自我重试的机制。if (!ob || !present(ob)) return; 这行代码原本就没有"重试"这个选项——如果角色在鬼门关等待的这 5 秒内因为任何原因暂时不在场(卡顿、正在被别的 move() 挪动),整个复 活/转生序列会直接永久放弃,没有任何重试或提示。已经拆成 !ob 才真正 放弃、!present(ob) 则用 call_out("run", 5, ob) 重新排队重试(沿用 这个档案自己原有的 5 秒间隔)。另外确认了同目录下 npc/yanluo.lpc 的 death_stage() 虽然名字很像,但它自己的守卫只有 if (!objectp(ob)), 根本没有 bundled 的 !present() 检查,是一个假阳性,没有这个 bug。

未继续测试的部分

时间关系,没有走出武馆去实际打怪(等级门槛挡住了,需要先在武馆内练 功/拜师升到足够等级)、没有实际触发一次死亡去验证这次修好的鬼门关流 程本身、也没有测试邮件/帮派/任务系统。下一轮如果继续深挖,建议先在 武馆里找教头或师父问问"拜师"相关指令提升等级,再出武馆找真正的怪物 测试战斗和死亡循环。

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

和 dtsl 是同一血统。

更正(2026-08-05):§7.68 复活软锁"修复"已撤销

上面提到的"鬼魂离开/不在场时被永久放弃复活流程"曾被当作 AGENTS.md §7.68 记录的一类 bug 修复(把单次判定改成每 5 秒重试)。经用户指出并 重新审视:这更可能是有意的游戏设计,不是 bug——大多数这类档案里 鬼魂根本无法自行移动,所以"不在场"要么从未真正发生,要么是"离开去 在阴间游荡,想回来时再走回这个房间、流程会通过 init() 重新从头开始" 这种有意为之的宽松机制,而不是需要强制追上玩家的错误。强行重试还可能 引入新问题:如果鬼魂之后又走回这个房间,旧的重试和 init() 重新触发的 新一轮流程可能同时运行,导致对话重叠错乱。已把这处改动撤销,恢复成 原始的 if (!ob || !present(ob)) return; 单次判定写法(bmxkx2001 除外——那份档案里这确实是一个真实存在、经过实际复现验证的 bug:鬼魂 本身完全无法移动,是另一个不相关的 NPC 强行把鬼魂拖走导致的)。详见 AGENTS.md §7.68 顶部的撤销说明。

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

Deep functional test round two (2026-08-14)

Independently re-verified against current code rather than trusting the round-one writeup above. Found and fixed two real, live-reproduced bugs neither previous pass caught, both root-caused to the same admin-account symptom: the fluffos account's real saved startroom silently failing to load, falling back to the void room.

New fix 1: cmds/wiz/update.lpc — present(file, environment(me)) crashes when the caller has no environment

Byte-identical to a bug found and fixed on xiakexing2017 earlier this session — same file shape, same crash line:

// BEFORE:
if ((obj = present(file, environment(me))) && interactive(obj))
// AFTER:
if (environment(me) && (obj = present(file, environment(me))) && interactive(obj))

Reached immediately on this pass's first admin login: environment(me) was 0 because the admin's saved startroom load had already failed (see fix 2 below) and dumped the character into /obj/void before update was even typed.

New fix 2 (the actual root cause): adm/daemons/race/human.lpc's query_action() indexes combat_action before it's populated, crashing during a reentrant first-load of the daemon — which silently sends the admin's real saved room's force-loaded NPC init chain into move()'s void-room error fallback

File:line: adm/daemons/race/human.lpc:132.

``lpc // BEFORE: mapping query_action() { return combat_action[random(sizeof(combat_action))]; } // AFTER: mapping query_action() { if (!sizeof(combat_action)) return ([]); return combat_action[random(sizeof(combat_action))]; } ``

Standard checklist gap found and fixed

adm/simul_efun/file.lpc had the common §7.11-class gap: cat() had no null-guard, log_file() had no assure_file() guard. Fixed both, with the forward declaration this session's dtsl pass just learned is required by this driver (log_file() is defined before assure_file() in this file too). Already confirmed correct, no action needed: log_error()'s severity gate, the §7.12 tell_room() fix (this lib shares dtsl's lineage and already has exclude || ({})), the §7.68 ghost-retry revert (confirmed still reverted to the single-check form), no §8.9 food/water issue, the one printf("%O\n", ob) is already commented out.

Investigated and ruled out as a leak: TMI()-wrapped strings ("login input_passwd", "login ok") showing as raw text on screen

These looked like leaked internal debug/state strings at first glance — they're not. include/tomud.h defines TMI(x) as TMA+x+TMB where TMA/TMB are the literal control characters \x19/\x1A — a Tomud (ToMUD/LLMud) client-protocol marker convention, meant to signal UI state to a Tomud-aware client (this lib's own welcome banner explicitly says "配合使用llmud客户端" — "designed for use with the llmud client"). A plain telnet client doesn't strip or interpret the control characters, so the wrapped label text shows through raw. Genuine protocol content, not a bug — left untouched.

Verification method

Booted native build-debug driver, admin login (fluffos/Mud@2026), update /adm/daemons/logind and a real look as the checks that actually caught the two bugs above. Two rapid consecutive admin reconnects, both clean. Driver killed by exact PID after each reboot (two total: initial boot where both bugs were found, clean reboot for fix verification); incidental fluffos.o save-timestamp churn reverted before commit.

Files modified this pass

§7.100 扫描修复(ROOM 基类多余 replace_program())

#define ROOM "/std/room":删除 686 处多余的、独立成行的 replace_program(ROOM);(保留 inherit ROOM;),与手足档案 dtsl2/dtsl 同一血统同一形状(obj/roommaker.lpc 字节级一 致)。其中 685 处由脚本自动删除,另 1 处在 obj/roommaker.lpc 第 138 行——"克隆我所在的房间"命令的字符串拼接模板把同一枚多余调用 烤进了每一个新克隆的房间,已同步手动修正。修复后全库仅剩 82 处历 史遗留的 //-注释掉实例,均确认无害、未改动。已用 build-debug 驱动干净启动验证(0 个新增编译错误,端口 40015 正常监听, 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): 4 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.

深度功能测试第二轮(§10.7,round-four,2026-08-21)— 真正打穿了战斗/死亡循环

上一轮(见上方"深度功能测试(§10.7,本轮)"一节)被石龙武馆的等级 门槛(combat_exp < 20000 才能从走廊往南出武馆,d/slwg/zoulang1.lpc 的 valid_leave())挡住,没能走到真正的野外战斗。这一轮延续 NOTES 里给出的思路,先确认 cmds/std/kill.lpc 本身没有任何专门针对 NPC 的战斗开关(和手足档案 dtsl2 一样;这里有的只是合理的 PK 相关 门槛:房间 no_fight、骑马中、正带兵打仗、target_id 白名单、同帮会/ 结盟帮会互杀保护、valid_kill.h里的 PK 冷却——全部只在双方都是 userp() 时才生效,对打 NPC 完全不受影响),然后用测试角色(id 见 存档 qincesi,密码已略去)+ 直接编辑其存档文件补上 "combat_exp":25000(满足武馆真实等级门槛的合法捷径,武馆内 张教头 NPC 的"武艺"→lianwu <项目>→task训练循环本身也证实是真实可用、 只是很慢的正规刷法,每轮约 10-15 秒得约 20-30 点经验,到 20000 点 理论上要刷 800+ 轮)后,成功出武馆,一路(西大街→...→北门→官路 系列→草地→高邮湖→树林→小溪)走到扬州北郊的真实野外(d/yangzhoubei + d/outyang)。

真实战斗验证:在小溪(d/yangzhoubei/xiaoxi1.lpc)用kill tu 攻击一只野兔(npc/yetu.lpc),走完完整的多回合战斗(攻击/闪避/命中/ 昏迷/死亡尸体),全程消息正常、debug.log保持空白。

真实死亡验证(本轮最重要的结果):转往山路(shanlu2.lpc)找到 更强的强盗(npc/qiangdao.lpc,combat_exp高达200000),kill dao 后几回合内被反杀,hp 打到负值,角色死亡→正确移动到DEATH_ROOM(鬼 门关,d/death/gate.lpc一带)→再传送进阎罗大殿,npc/yanluo.lpc 的death_stage()五阶段对话(阎罗王的一连串台词,每阶段 call_out 5秒)完整跑完→reincarnate()→按角色combat_exp>=20000正确判定路 线,被送到REVIVE_ROOM(/d/yangzhou/hotel,"有间客栈",不是 zoulang1也不是JIANYU)。全程debug.log保持空白,score/look 复活后完全正常可玩(hp回满、combat_exp因为角色age只有10岁、 lose()里age>12才扣经验的门槛没触发而保持25000点不变——这是刻意 的新手死亡保护设计,不是 bug)。

顺带验证了§7.68撤销后的行为在真实死亡下依然正确:测试过程中 为了分批发指令,多次主动断线重连(在 5 秒一阶段的death_stage() call_out窗口期间断开又重连),这恰好模拟了"鬼魂在这几秒内暂时不在 场"的场景。npc/yanluo.lpc的death_stage()本身已经带有专门的 in_death_stage临时标记防重入锁(详见文件内联注释,是本档案自己 之前某一轮已经补上的§7.112类修复,不是这一轮新做的),整个死亡对话 链条没有出现重复叙述、没有双重结算死亡惩罚,reincarnate()只完整 执行了一次——确认锁是有效的,且确认了这份档案自己另一个"看起来像但 其实没有这个 bug"的d/death/gate.lpc(NOTES上一节记录过,其 death_stage()只有单一的if (!objectp(ob))守卫、没有!present() 分支,是假阳性)本身也确实完全没被触碰到——本轮走的是npc/yanluo.lpc 这条不同的死亡对话路径。

跨库共享 bug 扫描:按本轮任务单点名的四个已知跨库 bug 逐一排查, 均不适用于本档案——adm/daemons/combatd.lpc里完全没有bounce相关 代码(不是这个架构);全档案没有chacha.lpc文件; adm/daemons/natured.lpc里没有if (!userp(ob[i])) destruct(ob[i]) 这一行;cmds/std/go.lpc里没有sizeof(exit[arg])这个写法(用的是 undefinedp(exit[arg])判定,形状不同)。四个都确认不适用,未做任何 改动。

未继续测试的部分——已逐一销项

1. 真实战斗和死亡/复活循环:本轮已完整走通,见上。 2. 邮件系统:确认这份档案根本没有面向玩家的站内信/寄信功能 ——没有mail/letter类玩家指令,没有"邮局"房间,没有"寄信"动 词。adm/daemons/sendmaild.lpc是一个真实的出站 SMTP 邮件发送后 门(大概率是账号注册确认邮件用的基础设施),但全档案搜索没有任 何地方调用它——是完全未接入游戏逻辑的遗留代码,不是玩家可用的邮 件系统。此项无法测试,因为功能本身不存在,不是 bug。 3. 门派/帮会系统:分两套系统分别验证。(a) apprentice拜师指令 (门派):用管理员账号(男性)在d/luoyang/zhonggulou向候希白 (花间派第五代传人)拜师,被正确以"你的相貌,唉,叫我怎么收你为 徒?"回绝——代码里确认是刻意的gender=="女性"门槛(花间派是原著 设定里的女性门派),无崩溃,debug.log干净,是有意的游戏设计。 (b) enroll加入帮会指令(势力):向海沙帮帮主 NPC韩盖天 (d/haisha/npc/bangzhu.lpc)申请加入,被"我不是首领,你和我说没 用的"回绝。追查发现adm/daemons/orgd.lpc的create()会 restore()覆盖代码里leader数组的默认初值(原本应含六大帮派的 六个 NPC 首领 id),而持久化存档work/data/orgd.o里leader字段 是空数组({})——导致is_leader()对任何帮派都返回假,全库范围内 "向 NPC 帮主申请入帮"这条路径永久失效。追查了这个字段唯一的写入 路径:adm/daemons/combatd.lpc里,玩家在战斗中击败当前的帮派首 领(无论是 NPC 还是玩家)时会调用ORG_D->del_leader()把该帮派从 数组中移除、并把shili/leader转移给击败者——也就是说"六个帮派的 首领都已经被玩家夺权过"是这套机制下完全合理、可达的正常游戏历史 终态。核对了同血统的dtsl/dtsl2两个手足档案的orgd.o, leader字段同样是逐字节一致的空数组({}),且此文件从最早的档案 转换提交起就已经是这个状态(非本轮或近期任何一轮引入)。没有编 译错误、没有崩溃、没有debug.log报错、没有驱动层拒绝——符合"没 有错误信号大概率是内容设计"的项目准则,加上三份独立档案状态完全 一致,判定为已归档的真实游戏历史状态,不是数据损坏,未做任何修 改。 4. 迷题(任务)系统:quest指令(显示已开放迷题列表/具体某个迷 题的提示文档)直接测试通过——列出全部迷题分类(含门派迷题分类), 查看具体一篇(长生诀)内容渲染正常,debug.log干净。 adm/daemons/jobmond.lpc("任务监控系统",扬州府衙一类可占领城 市的战斗任务播报后台)是纯被动的频道播报后台,没有玩家指令直接 调用它,本轮多次数小时的干净起服本身已经隐含验证了它不会主动抛 错。

标准清单快速过一遍的结果

§7.90(eval-cost)、§7.100(ROOM 多余 replace_program)、§7.111 (standard_trace)、§7.112(death_stage 重入锁)、§7.79(addn 二参 数)、§7.108、§7.30(feature/skill.lpc已有mapp(x) ? x : ([]) 守卫)——均为之前几轮/跨库扫描已处理完毕的项目,本轮没有发现新的 遗漏实例。全程使用干净的 build-debug 驱动起服,从开服到测试结束 debug.log全程零报错。

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

新角度:石龙武馆兵器铺付钱购物 + 净念禅院虚尘拜师正例。先前只测过 没钱的 buy dagger from tie jiang 拒绝,以及候希白「相貌」拒收男 弟子。本轮端口 40015。第一输入是英文 id(没有 GB 选单)。密码 Mud@2026。管理员存档这次没有 ghost。

实测过程

管理员 fluffos / Mud@2026(权限 (admin),落地大唐学院门口)。 clone /clone/money/gold 可用。score 走 GUI 协议行(score …), 师傅字段在性别后面:拜师前是「无」,拜师后是「虚尘」。

goto /d/slwg/bingqipu(石龙武馆兵器铺,铁匠 d/slwg/npc/tiejiang.lpc)。 买法是 buy <物> from <人>。list 长剑一两银子。buy sword from tiejiang 成功(「你向铁匠买下一把长剑」),讨价还价涨了一级。

goto /d/chanyuan/wuchang,虚尘(d/chanyuan/npc/xuchen.lpc)。 apprentice xu 一次成功:磕头、「恭喜您成为净念禅院的第八代弟子」。 cmds/usr/save.lpc 在 60 秒内再 save 会假报「档案储存完毕」却不写 盘;本轮用 call me->save() 落盘。杀驱动冷启动再登录:师傅仍是虚 尘,银子还在,长剑不进 autoload。虚尘只收男性、拒已婚、PKS≥5。候希 白仍只收女弟子。

发现并修复的 PROGRAMMING bug

1. 鬼魂收徒失败后 pending/apprentice 不清理(cmds/std/recruit.lpc, 与同日 dtsl2 同形,CRLF 按字节补):!living(ob) 分支已经告诉 双方「没有办法行拜师之礼」,却不 delete_temp("pending/apprentice")。 本轮 fluffos 不是鬼魂,没现场踩到;补丁形状与 dtsl2 已验证的修法 相同。

深度功能测试(§10.7,2026-10-04)— paths named in another case

The archive comes from a case-insensitive host: files are stored as BAGUA0.lpc, longjing.C, d/SHAOLIN/, the code names them in lower case (__DIR__ "bagua0", "tea/longjing", "/d/shaolin/..."), so the rooms, NPCs and items behind them could not be loaded (a .C file is not an .lpc file; KB 03 §4.5). scripts/lpc_case_paths.py --convert --apply dtslmud: 4 file(s) renamed to lower case (.C -> .lpc, .H -> .h) and 0 path reference(s) in 0 file(s) rewritten to the new spelling; 0 skipped, 0 ambiguous. Load check of the 7 objects that were renamed, edited or name a renamed path (HEAD against the working tree, an object is the same under any spelling of its path): 0 PASS -> 5 PASS, 0 that loaded before fail now, 5 more load.

4 files moved (4 .C); mostly under d/emeishan/npc (2), d/hangzhou/npc (1), d/luoyang/npc (1). A rescan with scripts/lpc_warnings.py dtslmud is still due: the renamed files are compiled for the first time.

深度功能测试(§10.7,2026-10-05)— room base survives a raising NPC

scripts/lpc_resilient_room.py --apply: make_inventory() of std/room.lpc loads each inventory object as if (catch(ob = new(file)) || !objectp(ob)) return 0;. new() of a file that is not there returns 0 (the room just lacks that NPC), but new() of a file whose create() raises (a skill file for set_skill() this archive never had, a carry_object() item that is not there, a header that never shipped) raised inside the room and the room did not load at all. The wrong-case renames of this lib (the section above) make the NPCs behind them reachable for the first time, so a room that holds one with such a content gap would have gone from "NPC missing" to "room missing"; now it loads without that NPC, as it did before. The room base compiles and loads with the change (HEAD against the working tree). KB 03 §4.5.

深度功能测试(§10.7,2026-10-05)— tail() simul_efun

scripts/lpc_add_tail.py --apply: cmds/arch/tail.lpc calls tail(file), an efun of the MudOS this archive ran on that FluffOS does not have (Undefined function tail: the command did not compile). adm/simul_efun/file.lpc now defines void tail(string file): the last 10 lines of the file, written to the player. Load check (HEAD against the working tree): 1 of the 1 tail command file(s) failed to load at HEAD, all load now, and the simul_efun file loads. KB 04 §6.2.

深度功能测试(§10.7,2026-10-05)— 房间列出的物件缺档时房间照常加载

房间基类 std/room.lpc:reset() 的 case 1: 拿到 0 就跳过这一项(if (!objectp(ob[list[i]])) break;)。档案里没有的 NPC 或物品(或 create() 出错的物件)原先会让列出它的房间整个加载失败(Bad argument 1 to environment(),或对 0 的 call_other()),连带这个房间后面的区域;现在房间照常加载,只是少了那个物件,下次 reset() 再试(KB 05 §7.25;scripts/lpc_resilient_room.py、scripts/lpc_room_reset_guard.py)。 验证:探针(scripts/lpc_room_reset_probe.py:房间基类列出一个不存在的物件,再调用 reset())HEAD 单件 崩溃、多件 OK,工作树 单件 OK、多件 OK。

深度功能测试(§10.7,2026-10-07)— 编译警告整理

scripts/lpc_warnings.py dtslmud --fix 加 scripts/lpc_fix_no_effect.py --apply,共改动 282 个 .lpc 和若干 .h。HEAD 与工作树分别加载(新进程):263 -> 263 通过,无回退;改过的头文件的 242 个包含者 202 -> 202 通过。 手工(dtsl、dtsl2 同改):

启动时警告 36 -> 0;新进程登录(新人物确认)正常。

深度功能测试(§10.7,2026-10-08)— .lpc 后缀的定宽切片

.c 改名 .lpc 时只改了字符串没改切片宽度:f[<2..] == ".lpc" 拿两个字符比四个字符,永远不成立(!= 永远成立),按后缀筛文件、去后缀的代码因此从不运行或截成 x.l(KB 06 §7.238)。scripts/lpc_lpc_suffix_slice.py --apply 改了 1 处比较、0 处去后缀(如 cmds/wiz/clone.lpc);改动的文件 HEAD 与工作树分别加载(新进程),无回退。

负数伤害当作消耗(2026-10-15,KB 06 §7.241)

receive_damage("neili", -52)、receive_damage("qi", -12) 之类把 add() 的负数写进了 receive_damage():本库 feature/damage.lpc 对负数伤害报错(或归零),练功/运功时报 F_DAMAGE: 伤害值为负值 或消耗失效。已改为正数伤害,内力/精力类改为 add(type, -N)(scripts/lpc_fix_damage_calls.py):/d/clone/biwutai/biwutai、/d/clone/room/leitai/leitai。

内力/精力当作伤害种类(2026-10-15,KB 06 §7.241 第二类)

本库 feature/damage.lpc 的 receive_damage() 不接受 jingli/neili/force 等种类,me->receive_damage("jingli", 30) 之类在练功、绝招、吸取时报 F_DAMAGE: 伤害种类错误。6 处改为 add(type, -N)(scripts/lpc_fix_damage_calls.py 第二步)。

其他体系的属性名(2026-10-15,KB 06 §7.241 第三类,--any-file)

24 个文件(技能、绝招、兵器、书籍、区域 NPC 等)来自侠客行体系(qi/jing/neili),而本库人物是 gin/kee/sen/force:receive_damage("qi", ...) 报 伤害种类错误,query("neili") 永远为 0。按 qi→kee、jing→gin(本库学习/读书指令消耗的就是它)、neili→force、max_neili→max_force、jiali→force_factor 改名(scripts/lpc_port_es_props.py --any-file)。

内力/上限当作伤害种类(补)(2026-10-15)

victim->receive_wound("force", n, me)(兵器吸内力)、receive_damage("max_qi", random(100))(走火入魔扣上限)等本库不接受的种类,9 处改为 add(type, -N)。

治疗/疗伤的属性名(2026-10-15,KB 06 §7.241)

receive_heal()/receive_curing() 也只接受本库的种类。1 个文件(多为只调用 receive_heal/receive_curing 的药物、NPC)来自侠客行体系,按 qi→kee、jing→本库学习指令消耗的池、neili→force 改名(scripts/lpc_port_es_props.py --any-file)。

NPC 带着本库没有的物品,载入失败(2026-10-15,KB 06 §7.73)

45 个文件、68 处在 create() 里 carry_object("<本库没有的文件>")->wear()(或 wield()、set_amount()):carry_object() 得 0,接着的调用报 Bad argument 1 to EFUN call_other(),NPC 根本载入不了,该有它的房间里就少了这个人。多是从别的系统搬来的 NPC 用了原系统的物品路径(最常见:/clone/misc/cloth、/u/smokes/junying/obj/blade、/d/city/obj/cloth)。每处改为 { object carried_ob = carry_object(...); if (carried_ob) carried_ob->wear(); }(scripts/lpc_guard_carry_object.py):NPC 照常出现,只是不带这件东西;文件补回来(或是虚拟对象)照样会带上。载入检查:改动的文件改前能载入 0 个,改后 19 个(19 个原来载入不了),没有退步;其余 26 个另有原因(多为 set_skill() 了本库没有的武功)。

去掉的预定义宏 SAVE_EXTENSION(2026-10-18,KB 04 §6.11)

老版 MudOS 给每个 LPC 对象预先定义了 SAVE_EXTENSION(".o");FluffOS 的 ChangeLog(COMPAT_BUSTER 一条)把它删了,改用 __SAVE_EXTENSION__。本库没有自己 #define 这个宏,用到它的文件一律 Undefined variable 'SAVE_EXTENSION',整个对象载入不了。改成 __SAVE_EXTENSION__(scripts/lpc_fix_predefined_macro.py):adm/daemons/network/netmail.lpc、d/zhiye/obj/old-zhujianlu.lpc、obj/token.lpc。token 是帮派领袖信物;netmail 是网络邮件守护进程(本库缺 mailer.h 等头文件,改后仍载入不了)。改后用 lpcc 逐个载入这些对象:2 个通过,1 个还有别的错误(adm/daemons/network/netmail:/adm/daemons/network/netmail.lpc:17: error: Cannot #include mailer.h)。

set_skill() 遇到本库没有文件的武功不再抛错(2026-10-18,KB 06 §7.256)

feature/skill.lpc 里的 set_skill()、map_skill()、prepare_skill() 碰到没有武功文件的名字就 error("F_SKILL: No such skill (…)"),调用它们的 NPC create() 整个中断:NPC 载入不了,刷出它的房间也少一个人。本库有 99 个对象设了本库没有文件的武功(120 个名字,最多的是 pojun-quan、sanhua-zhi、taizu-quan)。三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。抽样载入这些对象:30 个里 24 个通过,其余 6 个停在别的错误上(clone/npc/dzd_man:*No program in object '/clone/npc/dzd_man'!;clone/npc/tdh/fangdahong:*No program in object '/clone/npc/tdh/fangdahong'!……)。(scripts/lpc_guard_set_skill.py)

通向不存在房间的出口,按对面房间的回头出口改正(2026-10-18,KB 06 §7.257)

房间出口 "west": __DIR__"xxx" 指向的文件本库没有,玩家走过去就被卡住(笔误、改过名的房间,或者没带进档案的编号房间)。对面的房间存在、并且它相反方向的出口正好指回这个房间,说明出口原本就是通向它:/d/cahc/jinshuiqiao 的 north:wangfu → tongyuchi。改动后每个出口都能解析到本库的文件(scripts/lpc_fix_dead_exits.py;目标在运行时本来就能载入的库——虚拟房间——不动)。

补回本库没有的基类文件(2026-10-18,KB 04 §6.15)

下面这些基类本库没有,继承它们的对象(房间、NPC、物品)整批载入不了(inherit XXX; 的宏没定义,或者 inherit "/std/…"; 的文件不在)。同系的库里有同名文件,两库有大量逐字节相同的文件(亲缘关系),所以从那边原样拷来,缺的宏补进全局头文件 include/globals.h:/inherit/weapon/club(宏 CLUB)←shujian2008,9 个对象继承它,抽样 9/9 载入;/inherit/weapon/hook(宏 HOOK)←shujian2008,3 个对象继承它,抽样 3/3 载入;/inherit/weapon/brush(宏 BRUSH)←shujian2008,2 个对象继承它,抽样 2/2 载入;/inherit/weapon/weapon_combo(宏 WEAPON_COMBO)←xkxc98sj,2 个对象继承它,抽样 2/2 载入。每个基类补回后单独载入、再抽样载入使用它的对象,基类载不进来或者一个用户也载不进来的就撤掉(本库撤掉了它们,不再补)。(scripts/lpc_restore_bases.py)

调用了父类没有的函数的对象(2026-10-19,KB 06 §7.259)

::create();、::init();、return ::buy_object(who, item); 这类调用写的是父类里有这个函数的版本;本库的父类没有,旧驱动把它当空操作(返回 0),现在的驱动在编译时就报 Unable to find the inherited function,整个对象载入不了。单独成句的调用删掉,return ::f(...) 改成 return 0;(void 函数里是 return;),与旧驱动的行为一致。共 2 处、2 个文件:::ob_hit()(2 处)。只改了出错原因全是这一类的文件;还带着别的错误(多半是父类本身没了)的文件没动,等父类补回后再看。(scripts/lpc_fix_dangling_super.py)

simul_efun 丢了 unew()(2026-10-19,KB 06 §7.262)

adm/simul_efun/object.lpc 没有 unew()(带“独一无二”次数限制的 new():物件的 unique 值是允许同时存在的份数),而官府 NPC 和几件武器按 xkx 一系的写法调用 unew(...),于是 Undefined function unew,连同它们赋值给 object 变量的那一行都报错,整个物件载入不了。从同系的 xkxz2 把这个函数原样补回 object.lpc 末尾(scripts/lpc_transplant_function.py),受益物件 4 个:/clone/npc/guanfu/shi-qingshan、/clone/weapon/bestfalun、/clone/weapon/gold-falun、/clone/weapon/sword/jwqin。

全局头文件丢了 2 个宏(2026-10-19,KB 04 §6.16)

/include/globals.h 里没有定义 NAME_D、IP_D,用到它们的物件(名单里 2 个)编译时报 Undefined variable/Undefined function,整个物件载入不了(编译器在第一个未定义的名字上停下,后面的错误和警告都看不到)。同系的库在各自的头文件里都有这几行:照其中和本库共有文件最多的那个库,原样补到 /include/globals.h 最后一个 #define 之后,宏体里出现的路径在本库里都存在。补完后名单里有 2 个载入,其余还差别的东西。(scripts/lpc_restore_defines.py)

RANK_D 对 0 的称呼(2026-10-19,KB 06 §7.273)

adm/daemons/rankd.lpc 的 query_respect()、query_rude()、query_self()、query_self_rude() 里 4 个函数开头补上 if (!objectp(ob)) ob = this_object();。NPC 在 create() 里写 "…" + RANK_D->query_respect(this_player()) + "…"(inquiry 对话、chat_msg),驱动自己跑 reset() 时没有人在场,this_player() 是 0。旧驱动里 0->query("age") 返回 0,称呼走默认分支;新驱动直接报 Bad argument 1 to EFUN call_other(),房间的 make_inventory() 吞掉错误,被打死的 NPC 再也刷新不回来。用 scripts/lpc_reach_census.py --walk 找到(xkx100 的武当弟子、一灯、汴梁书生…);修在守护程序里,不动 1,800 个 NPC 文件。

两个斜杠开头的物件路径(2026-10-19,KB 06 §7.276)

1 个文件、1 处物件路径写成 //…(如 carry_object("//clone/cloth/hic_cloth")):/d/gaoli/npc/weishi.lpc。当年的驱动不介意(路径先被规范化);新驱动把原样的字符串交给 master 的 valid_read(),securityd 按 //clone 查目录,NPC 的 euid 没有权限,报 Read access denied.,NPC 载入不了。改成一个斜杠。

x != "a" || x != "b" 和 x == "a" && x == "b":同一个值的比较链永远成立/永远不成立(2026-10-20,KB 06 §7.282)

if (arg != "wan" || arg != "丸") return notify_fail(...) 对任何 arg 都为真,dir == "south" && dir == "north" 永远为假:条件下面的守卫要么永远拦、要么永远不拦。作者要的是“既不是 A 也不是 B”(&&)或“是 A 或 B”(||)。!= 链的 || 改成 &&(仅当条件下面是 return 的拒绝/守卫),== 链的 && 改成 ||(scripts/lpc_fix_cmp_chain.py)。改动:/cmds/adm/log.lpc(1 处)。

call_out_info() 的每一项只有 3 个元素(2026-10-20,KB 06 §7.287)

驱动返回的是 ({ 物件, 函数, 延迟 }),不带 call_out 的参数,旧代码读第 4 项 info[i][3](callouts 指令、calloutd、cod 工具)一读就报 Array index out of bounds,指令从第一行起什么也打印不出来。改为 (sizeof(info[i]) > 3 ? info[i][3] : 0)(scripts/lpc_fix_callout_info.py)。改动:/cmds/arch/callouts.lpc(1 处)。

if (x = 常量):条件里写成赋值的比较(2026-10-20,KB 06 §7.290)

if (arg = "button" || arg = "按钮")、if (!arg || arg = "") return 0;、else if (check = 0 && tempB != ""):把 == 写成了 =,条件的值就是那个常量,分支对所有非零数和任何字符串(连 "" 都算)永远成立、对 0 永远不成立,变量还被覆盖;驱动对此没有任何警告。每一处 = 改成 ==(scripts/lpc_fix_cond_assign.py,共 15 处)。改动:/adm/daemons/competed.lpc(1 处)、/clone/npc/mj.lpc(3 处)、/cmds/std/duanzao.lpc(8 处)、/obj/mj.lpc(3 处)。

callouts:sizeof(info[i]) == 4 把每一行都挡掉了(2026-10-20,KB 06 §7.287)

驱动的 call_out_info() 每项只有 3 个元素,输出行前的守卫 if (sizeof(info[i]) == 4) 永远不成立,指令只打印表头。改为 >= 3。改动:/cmds/arch/callouts.lpc(1 处)。

登录密码超过 255 字节:crypt() 抛错(2026-10-20,KB 06 §7.294)

驱动的 crypt() 对超过 255 字节的密钥直接报错(Error in crypt(), check your salt),老驱动则按任意长度散列。在登录提示符下输入 256 字节以上的密码,密码检查那一行就抛错,连线停在一个裸的 > 提示符上,登录走不完(password 指令的旧密码检查同样);用 300 个 x 当参数的扫描发现的。/adm/obj/simul_efun.lpc 里补了一个同名的 simul_efun crypt(key, salt):只有超长的密钥才被截短(先截到 255 个字符,再逐字符截到 sizeof(to_buffer(key)) <= 255,限制按字节算,一个汉字占三个字节),再交给 efun::crypt();普通密码原样散列,已存的密码散列不受影响(scripts/lpc_add_sefun.py crypt)。

valid_read() 不信任 sefun 对象的 euid "NONAME"(2026-10-20,KB 06 §7.295)

启动时 Too deep recursion. 两次(/adm/simul_efun/file.lpc 的 log_file() → assure_file() → file_size() → 主控 valid_read() → SECURITY_D->valid_read() 拒绝 sefun 对象(它的 euid 是 "NONAME")→ 拒绝记录又调 log_file()),之后每次 log_file() 都递归,日志文件一个也写不出来。valid_write() 早就信任 "NONAME",valid_read() 补上同样的判断(securityd.lpc)。

访客计数 howmany_visitor() 读文件不检查(2026-10-20,KB 06 §7.301)

howmany_visitor() 读访客计数文件(VISITORS / COUNTER / BBSVISITOR,只有运行中的 mud 才会写;.gitignore 把它排除在仓库之外),新检出的树里没有,read_file() 回 0,sscanf(0, ...) 报错。这份副本登录时不一定走到它,但调用时同样会出错。在读文件之后加了 if (!stringp(content)) return 0;(xkx100、xiakexing100、zzfy 早先的写法,scripts/lpc_fix_visitor_count.py)。改动:/adm/daemons/logind.lpc、/feature/logind.lpc。

get_config(__MUD_PORT__) 经旧版 runtime_config.h 得到字符串(2026-10-20,KB 06 §7.302)

本库的 include/runtime_config.h 是老 MudOS 的编号(整数设置从 15 起,本驱动从 256 起),get_config(__MUD_PORT__) 读到的是另一项设置(字符串),(int) 又只是编译期标注,于是 socket_bind(fd, LOCAL_PORT()) 报 Bad argument 2 to socket_bind(),版本守护进程 port = get_config(__MUD_PORT__) + VERSION_PORT 是字符串相加。头文件自己的注释写明 get_config(__MUD_PORT__) == __PORT__,所以代码里的这个调用(连同外面的 (int) / to_int())改成了驱动的预定义 __PORT__(scripts/lpc_fix_mud_port.py)。改动:/include/getconfig.h(1 处)。

NPC 的 chat() 在 chat_msg 为空数组时下标越界(2026-10-21,KB 06 §7.334)

NPC 基类的 chat() 是 if (arrayp(msg = query(...))) { if (random(100) < chance) { rnd = random(sizeof(msg)); if (stringp(msg[rnd])) ...,不检查列表是否为空。本库现有的 NPC 文件里没有字面写成空 chat_msg 的(扫描只认 set("chat_msg", ({ })));守卫挡的是运行时才为空的列表和以后新增的 NPC。在 random(100) < chance 前面加 sizeof(msg) &&(scripts/lpc_fix_chat_empty.py;if (random(100) > chance) return 0; 的写法是 !sizeof(msg) ||)。改动:/std/char/fighter.lpc、/std/char/jobnpc.lpc、/std/char/npc.lpc、/std/char/npcbak.lpc(共 4 个文件)。

不带参数的指令对 0 调用 efun:不在 wiz / adm 目录里的指令(2026-10-21,KB 06 §7.336)

指令敲成不带参数时,驱动把 0 传给字符串参数;下面的指令第一句就把这个 0 交给 efun,运行时报 Bad argument 1 to ...。它们在 cmds/std、cmds/usr、cmds/bos、cmds/app 这类目录里,上一轮的扫描只看巫师目录。用 scripts/lpcc_bare.py 不登录、在一次 lpcc 求值里以 0 调用 main() 逐个确认后,由 scripts/lpc_fix_bare_arg.py --all-dirs 在第一处使用参数的语句前加守卫:没有 help() 的是 return notify_fail("…\n");。改动(2 个文件,括号里是接住 0 的 efun):/cmds/std/actionavailable.lpc(sscanf)、/cmds/usr/info.lpc(present)。加载检查 HEAD 与工作树对照,没有退步。

注册到不存在函数的 add_action(2026-10-21,KB 06 §7.349)

add_action("函数", "动词") 绑定的函数,在对象本身、它包含的头文件、它继承的父类和继承它的子类里都没有定义(scripts/lpc_scan_callout_targets.py 静态解析,子类用传递闭包复核,scripts/lpc_verify_handlers.py 运行时 function_exists() 复核)。驱动从玩家最新的一条动作往回找,遇到这样的句子就报 *Function for verb 'drop' not found.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 32 条注册(20 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/clone/npc/flowergirl.lpc(do_buy、do_list)、/d/changan/npc/cha.lpc(do_teach)、/d/chanyuan/npc/buchen.lpc(do_answer)、/d/chanyuan/npc/buju.lpc(do_answer)、/d/huashan/xiaqiting.lpc(do_pass、do_resign)、/d/luoyang/npc/duguce.lpc(do_fenglu、do_shengzhi)、/d/quest/poison/yangxu-yan.lpc(do_teach)、/d/quest/toutianjianfa/mazei.lpc(do_ok)、/d/xiangyang/npc/cha.lpc(do_teach)、/d/xiangyang/npc/dutu.lpc(do_start) 等共 20 个文件。最多的函数名:do_buy 10 处、do_list 10 处、do_teach 3 处、do_answer 2 处、do_pass 1 处。没有动(读过):/clone/npc/huolang.lpc 的 do_buy(对象本身编译不过,登记留着,等对象能加载再处理)、/clone/npc/huolang.lpc 的 do_list(对象本身编译不过,登记留着,等对象能加载再处理)、/d/changan/npc/guanchai.lpc 的 do_ok(对象本身编译不过,登记留着,等对象能加载再处理)、/d/chengdu/npc/guanchai.lpc 的 do_ok(对象本身编译不过,登记留着,等对象能加载再处理)、/d/jiujiang/npc/guanchai.lpc 的 do_ok(对象本身编译不过,登记留着,等对象能加载再处理)、/d/luoyang/npc/guanchai.lpc 的 do_ok(对象本身编译不过,登记留着,等对象能加载再处理)、/d/xiangyang/npc/guanchai.lpc 的 do_ok(对象本身编译不过,登记留着,等对象能加载再处理)。

基类的 add_action 登记:处理函数只在后代里定义,基类自己被加载时报错(2026-10-21,KB 06 §7.349)

房间、NPC、公告板这类基类,init() 里登记的 add_action("函数", "动词") 的函数只由继承它的后代定义;基类对象自己被直接加载(房间可以走进去、NPC 可以克隆)时,访客输入这个动词,驱动报 *Function for verb 'x' not found.(第一轮扫描把这些当成“有后代定义,是活的”放过了,scripts/lpc_verify_handlers.py 在基类对象上跑 function_exists() 才看出来)。登记前加 if (function_exists("函数", this_object(), 1))(第三个参数 1:默认 function_exists() 把 protected / private 函数当作没有定义),后代照常登记。共 1 处、1 个文件(scripts/lpc_fix_dead_add_action.py --guard):/obj/sboard/sboard.lpc(do_appoint)。

对象路径里的 ..:new(__DIR__ "../obj/x") 报 Read access denied(2026-10-22,KB 06 §7.351)

__DIR__ 是当前文件所在目录(带结尾斜杠),__DIR__ "../obj/x" 就是 /d/zhiye/npc/../obj/x;驱动的 legal_path() 不接受带 .. 的路径,new() / load_object() 直接抛 *Read access denied.(实测,find_object() 对它答 0)。这个库的 NPC / 房间基类不先把路径规范化,调用它们的 NPC 在 create() 里就失败,克隆不出来(房间的 objects 表悄悄少了这些 NPC)。改成规范化后的绝对路径(scripts/lpc_fix_dotdot_dir.py):/d/zhiye/npc/caikuang-shi.lpc(__DIR__ "../obj/tieqiao" → "/d/zhiye/obj/tieqiao"), /d/zhiye/npc/caiyao-ren.lpc(__DIR__ "../obj/yaochu" → "/d/zhiye/obj/yaochu"), /d/zhiye/npc/fangzhi-nu.lpc(__DIR__ "../obj/fangsuo" → "/d/zhiye/obj/fangsuo"), /d/zhiye/npc/yangcan-popo.lpc(__DIR__ "../obj/jiandao" → "/d/zhiye/obj/jiandao") 等共 6 处。

缺失物品的 new() / carry_object() 调用链加了保护(2026-10-22,KB 06 §7.73 补充)

new(路径)->move(...); / carry_object(宏("x"))->wear(); 这类语句,路径指向的文件在这个压缩包里没有:new() 返回 0,->move() 在 0 上报 Bad argument 1 to EFUN call_other()。在 create() 里这个对象永远加载不了,在 reset() 里第一个缺失物品之后的刷新每次 reset 都被跳过。2 个文件里 3 处(new)改成 { object new_ob = new(路径); if (new_ob) new_ob->move(...); }(scripts/lpc_guard_carry_object.py --new:路径按这个文件自己看到的宏解析,宏名冲突、虚拟对象路径一律跳过)。 对这 2 个改动文件做加载检查(HEAD 与工作区对比):HEAD 能加载 1 个,现在 1 个(新增可加载 0 个,回归 0 个)。

调用了、但这个库自己的守护进程里没有的函数(2026-10-22,KB 06 §7.354)

MACRO->函数() 调用的函数在这个库的守护进程里不存在:call_other() 对不存在的函数不报错、只返回 0,所以这些功能(时间显示、昼夜事件、名字检查、价格文字……)一直没起作用。同一血统另一个库的同名守护进程里有这些函数,原文照抄到本库守护进程末尾(scripts/lpc_port_function.py;调用处传的参数个数、函数体用到的函数 / 全局变量 / 宏 / 字符串键都在本库里有,已核对;守护进程在 HEAD 和工作区都能加载,每个函数用固定参数各调用一次:HEAD 返回 0,现在返回正常的值):/adm/daemons/chinesed.lpc ← bxsj:chinese_time()(9 处调用)。

不带前导斜杠的物品路径:缺失物品的 new() / carry_object() 调用链加了保护(2026-10-22,KB 06 §7.73 补充)

carry_object("d/city/obj/jiasha")->wear(); 这类路径不带前导斜杠的写法在这个驱动里按根目录解析(new("d/city/obj/changjian") 能加载 /d/city/obj/changjian),所以和带斜杠的一样:文件不在压缩包里时 new() 返回 0,->wear() 在 0 上报 Bad argument 1 to EFUN call_other(),整个 NPC 加载不了。此前的保护工具把这种写法当作「相对当前目录,没法判断」跳过了。1 个文件里 2 处(d/city/obj/flower_shoe、d/city/obj/pink_cloth)改成带保护的块(scripts/lpc_guard_carry_object.py)。 对这 1 个改动文件做加载检查(HEAD 与工作区对比):HEAD 能加载 0 个,现在 1 个(新增可加载 1 个,回归 0 个)。

log/ 下的日志子目录没有人建:写日志的 write_file() / log_file() 报错(2026-10-22,KB 06 §7.358)

这个库的代码往 log/ 的 1 个子目录写日志:log/guoku(d/luoyang/npc/zhao.lpc)。压缩包、git clone 和站点包里都没有这些目录(本地启动没有写过它们,gen_keep_dirs.py 没记),write_file() 对不存在的目录抛 *Wrong permissions for opening file /log/dlog/money for append. "No such file or directory",调用它的函数在写日志那一行中断,后面的奖励、标志、步骤都没有执行。本库的 log_file() 会自己建目录(实测,或读源码:nitan 系的 assure_file()),所以这里只记了直接用 write_file() / save_object() 写的目录。把 log/guoku 记进 scripts/wasm_keep_dirs.txt(scripts/lpc_keep_log_dirs.py;站点和 pristine 树按它建空目录)。证明(scripts/lpcc_eval.py,在 /adm/daemons 这类目录下的求值对象里写 /log/zzdirprobe/...,新进程)见 KB 06 §7.358 的实测表。

汉字按字节切片的老写法没有换成按字符:改法名 "澄" + name[2..3]、辈分判断 name[0..1] == "澄" 等 27 处(2026-10-22,KB 06 §7.360)

原档按字节算字符串区间(一个汉字 2 字节):name[2..3] 是名字的第二个字,name[0..1] == "澄" 问名字是不是以「澄」开头。转成 UTF-8 之后,这个驱动的区间和 strlen() 都按字符算(strlen("澄观") 是 2,"澄观"[0..1] 是两个字),老算术原样留着、意思全变:拜入少林 / 武当 / 峨嵋等派的玩家被改成错误的法名(二字名 张三 变成一个字的 澄,更长的名字取到第三个字);各派掌门的辈分判断(name[0..1] == "澄")永远不成立;改名官 zhuce 的长度检查按字节算,合法的名字一个也过不了。

本库 27 处(7 个文件):拼名字的赋值 6 处、与汉字常量比较 13 处、改名官的长度检查 7 处、两个名字切片互比 1 处。例:newname[2..3] → newname[1..1](/d/chanyuan/npc/bonze.lpc:80);newname[0..1] → newname[0..0](/d/chanyuan/npc/xuchen.lpc:196);name[2..3] → name[1..1](/d/clone/room/lingwu/chanyuan/liaokong.lpc:119)。scripts/lpc_fix_gbk_slice.py 把下标按字符重算(社区的 UTF-8 版 xkx100utf8 里就是同样的写法,作对照);没有改的:截断(name = name[0..7],现在最多 8 个字,只是放宽)、ASCII 的区间(fname[0..3] == "room"、ctime(time())[4..19])和上下文看不出是汉字的区间。证明(scripts/lpcc_eval.py,bixiecanyang;其余各库是同一份文本):让澄字辈掌门 attempt_apprentice() 一个名叫「张三」的访客,HEAD 的法名是 慧,改后是 慧三;抽样 28 个库的 HEAD / 工作树加载对比 0 退步。工具不幂等([0..3] 折半得 [0..1],又是对齐区间),改过的文件记在 scripts/gbk_slice_converted.txt,不要重跑。

长度检查按字节写,旁边的提示按汉字写:12 处(2026-10-22,KB 06 §7.362)

老档的 strlen() 数字节(一个汉字 2 字节),转成 UTF-8 后它数字符:提示写「必须是 2 到 6 个中文字」的检查还是 strlen(name) < 4 || strlen(name) > 12,于是两三个字的房名、帮会名、群名、聊天室名、称号被提示自己说允许的那句话拒绝,上限却是提示的两倍;留言板的标题检查 i < 4 || i > 24 || i % 2 还会拒绝所有奇数个字的标题(post 你好吗)。本库 12 处(4 个文件):if ((strlen(name) < 2) || (strlen(name) > 8)) {(/adm/daemons/marryd.lpc:263);if ((strlen(name) < 4) || (strlen(name) > 10)) {(/feature/logind.lpc:585)。scripts/lpc_fix_strlen_gate.py 以提示为准:条件里每个 strlen 门槛都恰是提示所说数字的两倍才折半,存长度的变量(i = strlen(arg);)的门槛和 || i % 2 奇偶项、其后循环的 i % 2 == 0 && 一并处理;提示里有英文 / 字母 / 字节的不动。证明(scripts/lpcc_eval.py,bxsj 的留言板,求值对象继承 NPC 并重写 edit() 记录调用,command("post 标题")):HEAD 拒绝「你好」和「你好吗」、放行 23 个字的标题;改后接受前两个、拒绝第三个;shenmo 的 named.lpc invalid_new_name("张三"):HEAD 回 名字只能是2-4个中文字。,改后回 0。抽样 24 + 15 个库的 HEAD / 工作树加载对比 0 退步。

按字节写的汉字循环、茶馆猜诗和同盟频道标题(2026-10-23,KB 06 §7.363)

老档按字节做「一个汉字」:while (i--) if (i % 2 == 0 && !is_chinese(name[i..<0])) 只检查偶数下标,奇数位上的拉丁字母(张a)会过;茶馆猜诗 mixup() 用 strlen(str)/2 再 j=j+j、str[k..k+1] 交换两字节;泥潭系 channeld 的 switch (strlen(fname)) case 4/6/8/10 和 strlen(family)==4/6 按字节长度给同盟 / 门派名加空格。本驱动的 strlen() 和区间都按字符算。scripts/lpc_fix_byte_char.py:去掉奇偶项、把区间改成 name[i..i],按字符重写 mixup() 并把 strlen(first)>=14 折成 7,频道标题的 case / 门派长度改成字符数(对照 aoxiangtianji)。本库 汉字循环 / is_chinese 区间 8 个文件。证明(scripts/lpcc_eval.py):sjplii 的 check_legal_name 在 HEAD 接受「张a」、改后拒绝,两边都接受「张三」;xkx100 的 clubpoem->mixup("离离原上草") 得到「离离原草上」(真交换汉字);nitan_san 两字同盟名得到 【 兄 弟 】。关键对象加载通过。

从末尾按字节取汉字、书剑开帮长度(2026-10-23,KB 06 §7.364)

§7.360 只折了数字下标的 [a..b],从末尾取的 [<2..<1](GBK 下一个汉字)和 [<4..<1](两个字)还留着:开帮 gcreate 的帮助写「二至六个汉字 / 3到8个英文字母」,检查却是 (strlen%2)==1 || <4 || >12,末字对 ({ "帮","会",… }) 用 gname2[<2..<1] 永远对不上;少林 / 峨嵋法名 "明" + name[<2..<1]、死讯 killer[<2..<1] != "。"、兵器改名的 can_name 同样坏掉。scripts/lpc_fix_tail_slice.py 只改汉字语境(对照非 ASCII 字面量、can_name / valid_group_name、拼名字),ASCII 补零 / ctime / 复数词尾 / .lpc 不动;[<2..<1]→[<1..<1],[<4..<1]→[<2..<1],开帮门槛按帮助改写。本库 从末尾切片 2 个文件。证明(scripts/lpcc_eval.py bxsj):HEAD 上 斧头帮[<2..<1] 对不上「帮」;改后 [<1..<1] 对上;长度放行 斧头帮/fut;"明"+"张三"[<1..<1] 得「明三」。

examine 缺 sefun process_bar/log_time/filter_color/all_interactive(2026-10-27,KB 04 §6.16 Also)

cmds/arch/examine.lpc 调用 process_bar / log_time / filter_color / all_interactive,本 lib 的 simul_efun 未纳入 util(同系 zjdy2008wzb 有)。对象无法编译。新增 adm/simul_efun/util.lpc(#include <ansi.h> + 上述四个函数,all_interactive 为 efun::users()),并在 adm/obj/simul_efun.lpc 中 #include。证明:HEAD 加载失败;改后加载成功。

深度功能测试(2026-10-27,§6.25 one-arg message_vision)

message_vision("…") 补第二参(me / TP / this_player())。

深度功能测试(2026-10-27,§6.10 未知 pragma save_binary/optimize)

删除本库中 FluffOS 不认的 #pragma save_binary / #pragma optimize… 行。