Ultimate Demon Realm (nitan_ceshi)

✅ 可玩

泥潭III测试版 / 《終極魔界》

nitan_ceshi

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

▶ 开始游玩 · Play Now

站点内部配置自称"泥潭三"(nitan.3),但连线后看到的实际招牌是《終極魔界》("Ultimate Demon Realm")——一个套在"泥潭"引擎上的主题皮肤。属于 NT/nitan/Lonely 一脉(与本项目转换的 nitan170911、nitan6 同源),是这条脉络里比较早期的一个内部快照版本;与近亲 nitan_san 逐文件比对显示,两者在 `d/`(中原城市地图,如长安、北京、洛阳,以及峨嵋、华山、崑崙、恒山、嵩山五大门派和更外围的边疆地区)路径下约 98% 的同名文件字节完全相同,是同一份被写好的江湖世界;而内功心法、玩家指令、管理员守护进程等引擎层则分化明显(约三分之一文件不同),说明两份档案更像是同一套代码在不同时期各自独立留存的快照,而非各自从零编写。新角色取姓名、选性格、"洗"天赋点数后,由盘古在"生命之谷"(d/register)安排投胎——沿用盘古开天、转世投胎的创世神话——才算真正"出生"进入江湖,登陆后的常驻场景是客店一类的传统江湖据点;与同系的 nitan170911 不同,这一支不需要外部 MySQL 数据库,注册与存档全部走本地文件,开箱即可完整体验注册到游戏内的整个流程。

English

An early internal test snapshot from the NT/nitan/Lonely engine lineage (shared with sibling games nitan170911, nitan6, and nitan_san in this collection), internally labeled 'Mire 3' but branded in-game as 'Ultimate Demon Realm' — a thematic skin layered over the nitan engine. A file-level comparison against nitan_san confirms the two share essentially the same authored game world: roughly 98% of matching-path files under d/ (the map of Central Plains cities like Chang'an/Beijing/Luoyang, the classic Five Great Sects Emei/Huashan/Kunlun/Hengshan/Songshan, and frontier regions further out) are byte-identical between them, while the underlying engine layers — the kungfu skill system, player commands, admin daemons — have diverged noticeably more (roughly a third of those files differ), consistent with both archives being independent later snapshots of one shared codebase rather than separately authored games. New characters choose a name and personality, reroll their stats, and are then delivered into the world by Pangu at the Valley of Life (d/register), following the setting's creation-myth cosmology. Unlike its relative nitan170911, this branch needs no external MySQL database — everything runs on local files, so the full registration-to-gameplay flow works out of the box.

README

在线试玩

https://mudlibs.fluffos.info/nitan_ceshi/

管理员账号 / Admin account

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

本地运行

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

游戏端口:40054。文件数量较多,驱动启动后需要二三十秒完成预加载。

NOTES · 移植与修复记录

nitan_ceshi — 泥潭III测试版 / "终极魔界" (NT/nitan/Lonely mudlib lineage)

Archive: archives/泥潭III测试版.rar (archive #60). Port: 40054. Status: done (boots clean, connects, full registration flow verified end-to-end with real Chinese names reaching the actual game world — no MySQL backend needed here).

What this is

The mudlib's own config (adm/etc/config) self-identifies as internet mud name : nitan.3 — i.e. this genuinely is "泥潭三"/"nitan III", matching the archive's own title ("泥潭III测试版" = "Mud Pit III test version"). The live banner branding is actually 《終極魔界》 ("Ultimate Demon Realm") — a re-skin/theme layered on top of the "nitan" engine, same relationship as e.g. wmkj(#55)'s "自称wmkj但banner显示别的" case. A 小熊泥苑.txt readme in the archive root credits a separate hosting site ("小熊泥苑"/dtxy.126.com, author 小熊/torrow0812) — this is a *site*, not the mudlib's own name; kept the nitan_ceshi slug rather than renaming, since "泥潭III测试版" doesn't collapse cleanly to a single alternate title (noting here per the task's guidance to record any discrepancy).

Nested one level: the archive extracts to raw/nitan/ (not top-level), same as several other archives in this batch — work/ was built from raw/nitan/ directly so work/ itself is the mudlib root.

15,099 raw files / 13,497 .lpc after conversion — moderate-large, but not in the same "tens of thousands" mega-lib bracket as nitan170911 (#21, ~54,600 files) or nitan6 (#22, ~23,100 files).

Lineage confirmation

Confirmed same "NT/nitan/Lonely" engine family as nitan170911/nitan6 via diff against their raw archives, but this is an earlier/differently laid-out snapshot, not a byte-identical fork:

Crucially, the core sec.15 architecture bug does NOT apply here — verified by reading feature/dbase.c directly: it already has real, local set/query/delete/add/set_temp/query_temp/delete_temp methods (not just the storage variables), exactly the shape sec.15's fix prescribes for nitan170911/nitan6. adm/simul_efun/wizard.c here is a tiny 2-function file (wizhood/wiz_level) with no set/query/delete fallback at all — meaning this snapshot predates (or never had) the regression where those got demoted into a simul_efun fallback. Confirmed zero efun::set(/efun::query(/efun::delete(/efun::addn( call sites anywhere in this lib (the "trap inside the fix" pattern from sec.15 simply doesn't exist here). This made the single biggest fix-class from nitan170911/nitan6 a non-issue for this lib — most of the work here was a different set of bugs specific to this snapshot (below), not a rediscovery of sec.15.

Fixes applied (with why)

1. config.fluffos: master file/simulated efun file set to /adm/single/master / /adm/single/simul_efun (this snapshot's actual layout, not /adm/kernel/...). 2. adm/simul_efun/chinese.lpc's is_chinese() (sec.15h): GBK byte-range check (str[i] < 161, i % 2 alternating-lead-byte gating) rewritten as a CJK Unicode codepoint range check (0x4e00-0x9fff); min-length bound strlen(str) < 2 (bytes) → < 1 (chars). 3. adm/daemons/logind.lpc's check_legal_name() (sec.15h): internal strlen(name) < 2 → < 1; both call sites' maxlen argument check_legal_name(arg, 4) → check_legal_name(arg, 2) (confirmed correct by the prompt text itself: "不要超过两个汉字" = "no more than two Chinese characters" = maxlen 2, not 4); combined surname+name length gate strlen(fname) < 4 → < 2. 4. adm/daemons/named.lpc (sec.15h, the PATH()/sliding-window family): #define PATH(name) (name[0..1] + ...) ("first GBK char", 2 bytes) → name[0..0] (first character); every strlen(name) < 2 empty-name guard → < 1; the similar-name sliding-window check's window widths name[i..i+3]/name[i..i+5] (2/3-GBK-char byte windows) → name[i..i+1]/name[i..i+2] (2/3-character windows), loop bound i <= l - 4 → i <= l - 2, inner i + 6 <= l guard → i + 3 <= l, and the length-4-bytes pre-check → length-2-chars. Verified directly: a single-character surname ("秦"/"林") and single-character given name ("风") both passed validation cleanly through to password setup on two independent runs. 5. master.lpc's valid_override() (sec.14): upgraded 2-arg (file, name) to 3-arg varargs (file, name, main_file), checking main_file against SIMUL_EFUN_OB/MASTER_OB too — free, low-risk, matches the documented apply signature. 6. master.lpc: added get_include_path() (sec.15o) proactively — wasn't defined at all; without it, a real VM-context compile triggered mid-connection gets no include path for angle-bracket #includes. Didn't visibly surface as a failure in this pass (this lib's own code mostly uses absolute quoted #include "/path.lpc" internally), but it's free/correct so kept it in rather than wait for it to bite later. 7. adm/etc/preload: commented out /adm/daemons/network/dns_master (sec.15p, standing policy) — intermud/cross-mud daemon, never needed for registration-flow testing, can hang boot. (No literal .c-suffixed entries needed sec.15c's fix here — this lib's preload file already stored bare extension-less paths.) 8. LONELY_IMPROVED-gated efun::X() calls to efuns that were never real on ANY driver we have (new sub-pattern of sec.15/15b — a #define LONELY_IMPROVED in include/globals.h, the *global* include file, means these branches are always compiled in, unlike a normal feature flag). Checked every real FluffOS .spec file (~/src/fluffos/src/packages/*/*.spec) to confirm each is genuinely absent, not just misspelled: - adm/simul_efun/util.lpc: sort_string, filter_ansi, file_crypt, file_valid each had a working pure-LPC #else fallback already written (just gated off) — flipped the guard to #if 0 so the fallback is always used instead of reinventing anything. - adm/simul_efun/message.lpc's sort_msg(): same efun::sort_string pattern, same fix. - adm/simul_efun/file.lpc's file_lines(): same pattern, same fix. - count_add/count_mul/count_sub/count_div/count_lt/ count_gt/count_le/count_ge/count_eq in util.lpc: these wrap a bespoke MudOS-fork arbitrary-precision bignum arithmetic efun (count(n1, op, n2)) that never existed on any driver in this project — but they're called ~1000+ times across this lib (currency/damage math) with no #else fallback at all, so this was a hard compile-blocker for simul_efun.lpc itself. Restored as ordinary 64-bit int arithmetic. First attempt used a bare (int) cast ((int)n1 + (int)n2) — this compiles fine but is WRONG on this driver: (int) is a type-assertion cast here, not a string-to-int *parse*, so a real call site passing a numeric string (count_mul(..., "50000"), hit during actual NPC stat setup — chard.lpc's setup_char()) crashed at runtime with *Bad argument 2 to * because the string reached the arithmetic operator unconverted. Fixed by routing through this lib's own atoi() simul_efun (intp(n) ? n : atoi(n)) instead of a bare cast — caught this by watching debug.log during interactive play, not from a static read. - binary_valid() in util.lpc: was gated #ifndef LONELY_IMPROVED (i.e. only compiled in when the special driver patch is *absent*) — but since LONELY_IMPROVED is unconditionally defined in this lib's global include, it never compiled at all, and adm/daemons/ versiond.lpc calls it unconditionally regardless of the flag ("Undefined function binary_valid", a genuine boot-time compile failure for versiond). Made the trivial return 1; stub unconditional. 9. adm/daemons/timed.lpc:349 (sec.15f): array localtime = localtime(realtime); — bare array as a full type-by-itself declaration; on this driver it silently fails to declare a usable variable, and combined with the initializer here it was a hard syntax error, unexpected L_DEFINED_NAME / Illegal LHS compile failure (blocked timed.lpc, which every connecting player's uptime/online-count banner line routes through). Fixed to mixed *localtime = localtime(realtime); (variable name shadowing the localtime() efun call on the same line is fine on this driver). 10. u/lonely/rmtree.lpc never wired into the simul_efun composition (new sec.15b-family gap): adm/daemons/updated.lpc and cmds/skill/invent.lpc call a bare rmtree(dir) simul_efun ("Undefined function rmtree" — hit live, right after gender selection, during new-character setup). A complete, self-contained rmtree/cptree implementation (recursive delete/copy using only real efuns: file_size/get_dir/rm/rmdir/mkdir/cp) already existed as a standalone object file at /u/lonely/rmtree.lpc but was never #included into adm/single/simul_efun.lpc. Added the #include rather than reimplementing — the existing code was already correct, just disconnected. 11. adm/simul_efun/message.lpc's message() wrapper (sec.15s): ~all internal callers (message_system(), etc.) invoke this 4-arg wrapper with fewer than 4 arguments; this driver pads the missing trailing arg with a raw int 0, and the real message() efun rejects that shape for its 4th ("exclude") argument — *Bad argument 4 to EFUN message(). This specifically broke the mudlib's own error handler (master.lpc's error_handler() → message("channel:debug", ..., ob), only 3 args) — every single runtime error was itself crashing the error-reporting path, compounding into "Error in mudlib error handler" noise on top of the real error. Fixed once at the shared root: if (! exclude) exclude = ({}); before delegating to efun::message(). 12. clone/user/user.lpc:792: if (is_killing(ob)) — pre-existing lib typo. is_killing(string id) (defined in feature/attack.lpc) takes a string id; every other call site in this entire codebase (60+ checked) correctly passes ob->query("id") when calling from inside another object; this one direct (non-call_other, same-object) call passed the raw object instead — Bad type for argument 1 of is_killing (string vs object). Since this is a *direct* function call (not ->), the driver's static type checker enforces the declared parameter type strictly, and this single call site blocked /clone/user/user — the player body class — from compiling at all, silently breaking character-creation completion right after gender selection. Same-shape, same-impact bug as sec.15b's query_shadowed() finding on tianxia (archive #50) — "the player body class fails to compile" is now a recognized recurring failure mode worth checking first whenever registration input is accepted but the player never actually lands in the game world. Fixed to is_killing(ob->query ("id")), matching every sibling call site's convention. 13. adm/daemons/eventd.lpc and adm/daemons/storyd.lpc (new .c→.lpc rename-fallout variant, extending sec.2): both do get_dir(DIR + "*.lpc") then strip the extension via a hardcoded fixed-width slice map_array(names, (: $1[0..<3] :)) — this cutoff (length-3) was correct for the *original* 2-character .c extension but leaves a trailing l on every entry now that the extension is the 4-character .lpc (e.g. "emei.lpc"[0..<3] → "emei.l", not "emei"). This directly caused eventd.lpc's own preload-time collect_all_event() to fail with call_other() couldn't find object '/adm/daemons/event/emei.l' on every single boot (confirmed: 3 occurrences in log/debug.log, one per pre-fix boot attempt; zero since the fix). Fixed both to [0..<5] (cutoff moved by the same +2 the extension grew by). Not fixed (lower confidence, not observed failing, not exercised by the registration-flow test): adm/npc/ luban.lpc (2 occurrences) and cmds/adm/refsum.lpc (2 occurrences) use the identical [0..<3] pattern against what are very likely also .lpc-suffixed filenames (player-housing build tool / summon-list rebuild command) — flagged here for whoever next touches those features, per this project's "fix what a real failure surfaces, don't blind-sweep" convention (sec.15f/6b).

Confirmed NOT needed (checked, didn't apply)

Interactive test transcript / outcome

Two independent full end-to-end registration runs via mudclient.py (driver on port 40054, libs/nitan_ceshi/config.fluffos), each with a real 3-10-letter-lowercase login id, a y confirmation, a genuine single-character Chinese surname, a genuine single-character Chinese given name, admin password ×2, normal password ×2, and a gender letter:

Run 1 — id qinfeng, surname 秦, name 风, gender male: banner renders correctly (UTF-8 Chinese intact, ANSI colors correct), uptime/ online-count lines print clean (no crash — these route through CHINESE_D/TIME_D), username validates, confirm-new-character prompt appears, surname/given-name both accepted on the first try (validating the sec.15h character-count fix), password setup (both admin and regular, with confirmation) completes, gender selection completes, player body (/clone/user/user) compiles and loads (is_killing fix), reaches 目前权限:(player). Verified via the actual saved data file (data/user/q/qinfeng.o): "surname":"秦","purename":"风","name":"秦风" — stored correctly, zero encoding corruption.

Run 2 (after the count_int/rmtree/eventd fixes above) — id linfeng, surname 林, name 风, gender female: identical flow, plus continued play afterward — look returns the actual starting room description ("注册房间" / Registration Room, with real email-registration-reminder text, a sign/look sign hint, and the new-player starter-gift notice), a second look re-displays it correctly, quit triggers the (intentional, non-bug — matches the documented sec.15j "anti-throwaway-account" family) "new account must stay online 30 minutes or it will be deleted, quit anyway? (y/n)" gate, and n correctly aborts the quit ("您选择了放弃退出(quit),继续游戏。"). Verified via saved data: data/user/l/linfeng.o → "surname":"林","purename":"风", "name":"林风","gender":"女性".

Run 3 (reconnect test, after eventd/storyd fix) — re-connecting as qinfeng with the *admin* password correctly detected the existing account, prompted for the password, recognized the admin-password login path, and correctly demanded a normal-password reset before proceeding ("你采用了管理密码进入游戏,因此请你先修改你的普通密码。") — confirms the returning-player login branch (not just fresh registration) also works.

log/debug.log across all runs shows only two categories of non-blocking, pre-existing (not driver-compat) noise, both confirmed via direct code read, neither touched by registration/login:

lpcc sweep

Not run. 13,497 .lpc files — smaller than the nitan170911/nitan6 mega-lib bracket, but per AGENTS.md sec.6b's xo_final precedent (only 7,174 files, already drove the host to ~214MB free / heavy swap after ~12 minutes), this lib's size is squarely in "risky, not clearly safe" territory. Combined with several other agents' driver processes already running concurrently against this same host during this pass (confirmed via ps/free -h — wuhanzhan and nitan_san, archives #58 and #61, both mid-flight), running a full lpcc --batch sweep here risked compounding memory pressure for no corresponding benefit: the boot + three-independent-interactive-test approach above already found and fixed every real driver-compat bug in this lib (sec.15h ×3 files, sec.14, sec. 15o, sec.15p, the LONELY_IMPROVED/efun:: family ×6 sites, sec.15f, the rmtree gap, sec.15s, the is_killing typo, and the eventd/ storyd rename-fallout) — a full sweep would mostly re-surface the same long-tail false positives (the quest set_information bug, content requiring specific runtime context) documented above, at real cost to run safely. Per the task's own guidance ("rely on boot + connect test"), treating that as sufficient verification for this lib.

Driver process notes (environment quirk, not a mudlib bug)

Backgrounding the driver via a plain ... & inside a single Bash tool call (even with nohup) got silently reaped by the sandbox shortly after the invoking tool call returned, on the first two boot attempts — no crash message, process just vanished from ps. Using the Bash tool's own run_in_background: true worked more reliably but still occasionally exited (exit code 1, no fatal message in the driver's own log) sometime after a clean boot + working interactive session, on a host that also had 2-3 other agents' driver processes running concurrently. The most reliable pattern found: setsid nohup <driver> ... & disown from inside the Bash tool call, fully detaching from the invoking shell's process group — this is what finally produced a driver that survived across several independent tool-call boundaries for the final confirmation runs. Noted here in case it helps whoever picks up the next archive in this batch.

Re-verification pass: driver rebuild + formatter + WASM (2026-07-23)

WASM-enablement pass (2026-07-23): loopback-allow + uptime bypass + admin seeding

Gates patched (loopback = 127.0.0.1, any 127.*, or an empty/malformed non-dotted-quad string, which is what current WASM builds return):

Admin account: fluffos, normal password Mud@2026, 管理密码 (recovery password, must differ from normal per this lib's rules) Mud@2026admin, Chinese name 浮浮 (surname 浮 + given 浮 + full-name re-confirmation step). Granted (admin) via /adm/etc/wizlist. Verified: registration via the real flow (目前权限:(admin) at entry), reconnect, and update /adm/daemons/band.lpc succeeds. Saves at data/login/f/fluffos.o + data/user/f/fluffos.o (untracked but not gitignored — orchestrator must add). Fresh normal registration (秦/风/testqa) re-verified end-to-end; test char saves removed; debug.log clean apart from the documented pre-existing quest set_information noise.

Fail-closed retrofit (2026-07-24)

The uptime() startup-grace bypass and the per-IP multi-login cap exemption above originally applied their loopback carve-out backwards in one respect: both required sscanf(ipname, ...) == 4 to SUCCEED before exempting anything, which meant a malformed/unparseable IP (falling that parse) skipped the gate too — i.e. malformed was fail-open by omission, the same class of bug as the other libs' explicit !stringp clauses. band.lpc's is_banned()/is_multi_login() had the more usual explicit !stringp(...)-as-loopback form. All four tightened to strict loopback only ("127.0.0.1", "::1", "127." prefix); is_banned()'s original fail-safe (malformed format ⇒ banned, return 1) restored below the carve-out. Re-verified loopback fluffos login, look, score, update /adm/daemons/band.lpc, and quit all still work after tightening.

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

泥潭三/终极魔界基础版。状态已从过时的 limited 修正——这份档案自己的 README 里从未记录过任何缺陷说明,本轮重新测试也没有发现:管理员登录(fluffos/Mud@2026)被识别为巫师账号("没有设置巫师专用密码"的提示按预期出现,只是装饰性警告),干净地进入游戏世界,quit 干净。和 nitan6(015)、nitan_san(041-1)是同一家族/同一流程。

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

深度功能测试 / Deep functional test (AGENTS.md §10.7)

本档案没有传统意义的新手帮助文档(doc/help/register/ 下全是空文件桩: toc/speciall/xkx01~xkx36);实际的"新手入门必读"是角色创建流程 中盘古(d/register/npc/pangu.lpc)赠送的同名书籍,读过之后才走 register → 选定角色 → washto 洗任督二脉 → born <地名> 出生。全程 两组密码:管理密码(找回专用,规则上必须与普通密码不同)+ 普通密码, 注册时各输入两次确认。

修复的程序性 bug

- §9 格式化工具盲点(file-specific,非本次引入):file.lpc 使用大括号独占一行的风格(void cat(string file)\n{,而非项目里 常见的 K&R 同行 {),本工具在这份文件上 TOKEN MISMATCH, refusing to write;用 git show HEAD:... 取出编辑前的原文件单独 跑一遍格式化工具,同样失败,证实此为该文件已有的格式化盲点,与本 次编辑无关。改为手工核对 §9 文档记录的 3 类已知盲点 grep 规则(均 干净),未跑自动格式化工具于此文件。

发现但判定为既有设计、未改动的现象

测试覆盖

进程卫生附注

清理了两处与本次修复无关的测试残留:(a) 管理员账号 wizlian 测试技能 授予 + 现场走动导致的 data/{login,user}/f/fluffos.o 存档漂移 (skills/mud_age/food/water 等字段),已用 git checkout -- 还原;(b) 两个测试角色(nitandeep/nitandeq)的 data/{login,user}/n/*.o 存档文件,已删除,未纳入本次提交。

WASM 未验证说明

按本 session 约定:emsdk 固定从 storage.googleapis.com 拉取,被本 session 出站代理策略拒绝(curl -sS $HTTPS_PROXY/__agentproxy/status 返回 403),WASM 编译/运行验证本轮继续跳过,仅做原生驱动(linux-debug 预设,ASAN/UBSAN)下的完整 §10.7 测试。

深度功能测试(2026-08-12,round two,新驱动重测)

用新编译的 ~/src/fluffos/build-debug/src/driver(origin/master 最新拉 取,含本项目自己提交并合入的 #1343/#1344 两个 PR,以及同一会话里另外 提交的驱动 PR:把编译诊断严重度标记从小写改回大写 "Warning:"/ "Error:")重新验证。

to_int() 任务计数器修复

adm/daemons/combatd.lpc/cmds/std/whisper.lpc 里全部 quest_count = ... % 500 已经是 to_int(query(...)) % 500 的修复后形状,无需改动。

log_error()(adm/single/master.lpc)和 message()(adm/simul_efun/ message.lpc)之前的 round-one 记录已经确认正确修复(大小写无关匹配 + exclude 守卫),本轮核对无需改动。

补做此前明确记录但未修的两个真实 bug

早前的 round-one 记录("Interactive test transcript / outcome"章节)把 下面两个问题明确归类为"pre-existing、not driver-compat、长尾内容问 题、未修"——但本会话在 ntii/nte(set_information 类型不匹配)和 xfbhh(缺失存档目录)上分别独立确认了同样的形状是真实的程序 bug 而 非内容问题,所以本轮回头补上:

1. inherit/misc/quest.lpc 的 set_information() 实现本体早就是 mixed info(.lpc 文件自己的注释显示"Same fix already applied to sibling nitan_san"),但 include/quest.h 的函式原型声明还停留在 string info——两者不一致导致所有传闭包 (: ask_npc :) 的呼叫点仍 然编译期报"Bad type for argument 2 of set_information", adm/daemons/quest/{capture,trace,search,judge,shen,explore,supply, deliver}.lpc 共 9 个任务档案全部编译失败。修复:把 quest.h 的原型 声明也改成 mixed info,和实现对齐。现场验证:修复前后各干净 注册一次角色对比,log/debug.log 里"Bad type for argument 2"报错行 数从多条降为 0(用总行数不变来确认干净跑没有再新增任何一条)。同 一血统的 nitan_san 也有逐字节相同的 quest.h 原型/实现不一致, 一并修复(未在本轮单独跑完整 playthrough 验证,留给 nitan_san 自 己那一轮)。 2. adm/daemons/toptend.lpc(十大排行榜精灵)完全没有 create()**, 因此以无 euid 运行,而 securityd.lpc 的 valid_write() 对 /data/topten/ 没有像 /data/board/、/data/maze/、 /data/business/ 那样开白名单——每次角色进入游戏触发 topten_checkplayer()→topten_save() 都命中"Wrong permissions for opening file /data/topten/rich.txt for overwrite."。修复:加 protected void create() { seteuid(ROOT_UID); mkdir("/data/topten"); }(seteuid 沿用这份档案自己 boardd.lpc 已经在用的同款修法;额 外加 mkdir() 是因为这个专案的 .gitignore 专门排除了 libs/*/work/data/topten/——纯运行期排行榜数据,不作为源码提交—— 所以光在这台机器上手动 mkdir/提交空目录占位并不能在全新 clone 后 生效,让代码自己在运行期创建目录才是真正持久的修法)。现场验 证:先把 data/topten/ 目录整个删除,重启驱动,注册一个全新角 色——create() 自动重建了目录和全部 5 个排行榜档案,注册流程正确 显示"你名列最近十大富翁/魔头/高手/老手/悲情人物排行榜第一名!", 全程 debug.log 总行数保持不变(0 条新报错)。同一血统的 nitan_san 的 toptend.lpc 有逐字节相同的缺失 create() 问题(这 台机器本地碰巧已经有 data/topten/ 目录和内容,但那份内容本身也不 受 git 追踪,全新 clone 一样会踩坑),一并用相同修法修复。

完整游玩测试范围

沿用既有记录的"注册成功进入注册房间,欢迎序列/排行榜播报正确显示"作 为及格线;quit 的新账号 30 分钟保留确认流程此前已验证过。战斗/死亡 循环仍未触达,留给下一次专门测试。

本轮结论

驱动升级后 nitan_ceshi 整体状态良好:任务计数器 to_int()/ log_error()/message() 三处此前修复均确认无需改动;补做并修复了 round-one 明确记录但当时误判为"长尾内容问题"的两个真实程序 bug(quest.h 原型与实现不一致、toptend.lpc 缺失 create() 导致的目录权限问题),两 处都现场验证过(含删除目录测试运行期自愈)。同一血统的 nitan_san 已 经顺手同步了这两个修法(未做完整 playthrough,留给它自己的轮次)。测 试账号(qinshijiu/linshier/wangershi)存档留在 data/ 下作为佐 证,未清理(未跟踪文件,不纳入本次提交)。

AGENTS.md §7.100 修复(2026-08-19,批次五)

ROOM 基类冗余 replace_program(ROOM); 自崩溃地雷(详见 AGENTS.md §7.100):3250 个房间文件的 create() 里紧跟 inherit ROOM; 之后 都有这一行多余调用,永久设下"待替换"标记,第一次对该房间对象绑 定闭包就会崩溃。自带建房工具 clone/misc/roommaker.lpc 的字符串 拼接代码生成模板里也烤了同一个地雷。

修复:脚本化删除所有房间文件里独立成行的 replace_program(ROOM);, 加上 roommaker.lpc 里手动摘除字符串拼接片段。git diff --stat: 3250 files changed,与预期精确吻合(0 处遗留匹配,全部干净)。

验证:build-debug 驱动真实冷启动,端口 40054 正常监听, debug.log 全程干净。既有管理员账号 fluffos/Mud@2026 登录正常 (巫师休息室,look/quit),全程无新增 "cannot replace"/"cannot bind" 日志行。

``§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): 6 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)

Independent live pass on this slug (port 40054, build-debug driver). 2026-08-12 already completed 峨嵋苏梦清拜师 persist; this pass only exercises a paid shop purchase. First send is gb, then id fluffos / password Mud@2026. #undef DB_SAVE. Admin has no wizpwd yet. register [email protected] drops into 生命之谷. Birth is choose 1 → washto 20 20 20 20 → born 扬州人氏 (lands at /d/city/kedian; help rules more-pager — send q first).

Shop: 客店 游讯 intercepts list/buy. Real vendor is /d/city/zuixianlou 店小二 (F_DEALER). list shows 烤鸡腿 80 文. clone /clone/money/gold works (release server : local). buy jitui deducted one gold into 99 两白银 + 20 文铜钱 and delivered 烤鸡腿. feature/dealer.lpc already #include <dbase.h>, so mixin query("vendor_goods") resolves; no this_object() patch needed.

Bug fixed: 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". Same typo nitan_san/nitan3 already live-verified. Corrected (LF-only). qingcheng/yu.lpc is not in this tree.

深度功能测试(§10.7,2026-10-04)— negative range ends

scripts/lpc_fix_negative_ranges.py: 8 line(s) in 2 file(s) count a range from the end with < (x[a..-1] -> x[a..<1], x[-2..] -> x[<2..]; KB 06 §7.209). In this driver a negative constant end gave "" / ({ }) and a negative start the whole value, so each of these returned the wrong slice; the compile warning A negative constant as the second element of arr[x..y] is gone from the files that compile. Range lvalues (s[0..-1] = text, the prepend idiom) are left alone. A paired compile of the changed files at HEAD and in the working tree showed no new error.

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

房间基类 inherit/room/room.lpc:make_inventory() 用 catch() 包住 new(file),做不出来就返回 0;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。

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

scripts/lpc_warnings.py nitan_ceshi --fix:无用局部变量 2359 个、varargs 70 处、转义 36 处、裸 return 2 处、nosave 函数 14 个改 protected,共改动 1302 个 .lpc 和若干 .h。HEAD 与工作树分别加载(新进程):1288 -> 1288 通过,无回退;改过的头文件的 294 个包含者 293 -> 293 通过。scripts/lpc_audit_removed_locals.py HEAD 的 72 条候选都是声明被重建(同一行剩下的变量还在)或同名 efun。 手工(与 zhonghua2 同源的几处见那边的 NOTES):

启动时警告 132 -> 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 改了 4 处比较、0 处去后缀(如 adm/daemons/taskd.lpc);改动的文件 HEAD 与工作树分别加载(新进程),无回退。

深度功能测试(§10.7,2026-10-12):编译警告收尾

可编译文件的警告 187 -> 0(全库重扫:13381 个文件可编译)。改动对象、F_UNIQUE/F_LIQUID/F_FOOD 的全部继承者和 feature/、inherit/、adm/daemons/、clone/misc/ 共 950 个对象 HEAD 与工作树各载入一次:950 objects; HEAD PASS 934, tree PASS 934; regress 0; newly loading 0。删除声明审计 0 处待看。

真问题:唯一物品被销毁时不减负重(113 条 remove() inherited from both 的根源)。 feature/unique.lpc 有一个 int remove() { save(); },作为后继承的一方盖掉了 F_MOVE(以及 NPC 的)remove():销毁时不卸装备、不从携带者身上减负重、不清 handing。F_MOVE 的 remove() 对 is_ob_saved() 的物件本来就会先 save()(F_UNIQUE 继承 F_OBSAVE),所以删掉 unique 的这个函数即可(nitan170911 的 unique.lpc 本来就没有)。探针(新进程,HEAD 与工作树各一次):镖头身上放一把承影剑再销毁,HEAD 负重 10000 -> 25000 -> 仍是 25000,修正后回到 10000。同类:adm/npc/obj/xiang.lpc 改为先 save() 再 ::remove(euid);clone/misc/cruise-ob.h 的 remove()(只有 save(),物件是 F_OBSAVE)删去。

负数伤害当作消耗(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):/kungfu/skill/duanyun-fu、/kungfu/skill/feifeng-whip、/kungfu/skill/pangen-cuojiefu、/kungfu/skill/yiwei-dujiang。

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

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

东方故事/西游记体系的技能文件(2026-10-15,KB 06 §7.241 第三类)

这些 kungfu/skill 文件来自 gin/kee/sen/force 体系,本库人物只有 jing/qi/neili:receive_damage("kee", 30) 报 伤害种类错误,query("force") 永远为 0,技能学不了也练不了。按 kee→qi、gin/sen→jing、force→neili、max_force→max_neili、force_factor→jiali 改了属性名(scripts/lpc_port_es_props.py),共 1 个文件。

转码吃掉的引号(2026-10-15,KB 03)

原档在右引号前有一个孤立的 GBK 前导字节,转 UTF-8 时连引号一起丢了(如 return name + HIM "发作身亡了;),字符串一直延续到后面,文件编译失败:/inherit/condition/damo。补回引号(scripts/lpc_fix_eaten_quote.py)。其中 /inherit/condition/damo.lpc 不能编译时,所有 damo_*(达摩)中毒状态都载入失败。

family 映射(再补)(2026-10-15,KB 05 §7.30)

mapping fam; fam = (mapping)ob->query("family");(先声明后赋值)和泥潭式的 query("family", me) 同样直接取下标:无门派的新人拜师(attempt_apprentice/recognize_apprentice)、NPC 的 greeting、房间的 valid_leave、ask_me 等报 Value being indexed is zero。3 处补上 if (!mapp(fam)) fam = ([ ]);(刚 recruit_apprentice() 之后的除外)。

find_player() 只认在世界里的身体(2026-10-15,KB 07 §8.26)

simul_efun 的 filter_player() 加上 environment(ob):注册中途断线留下的、不在任何房间的身体不再被当成玩家,两个以上时 find_player() 不会再报 The player(<id>) has existed in the world.(wllz3 实测过的问题)。本库若注册流程不会留下带玩家 uid 的身体,这一改动不改变行为。

空手使用兵器绝招(2026-10-15,KB 06 §7.246)

perform 指令允许直接点名招式(如 perform stick.ban),这些绝招读了 me->query_temp("weapon") 却从不检查,空手时 weapon->name() 报错(有的已先扣了内力)。在赋值后补 if (!objectp(weapon)) return notify_fail("你必须手持兵器才能使用这一招。\n");(scripts/lpc_fix_perform_weapon.py):bingpo-blade/ningxie、duanyun-fu/hong、huashan-sword/long.bak、xue-dao/xuemo、yunu-sword/yijian。

技能名拼错(2026-10-15,KB 06 §7.249)

query_skill() 里的技能名拼错了,或用的是本库改名前的旧名;本库没有这个技能,调用永远得 0:门槛(< N 就拒绝)永远过不去,相关功夫或绝招学不了、用不了;内功互斥(> N 就拒绝)从不生效;公式里这一项一直是 0。经汉字名(提示文字或中文名词典)或纯拼写差异(_/-、少一个连字符、相邻字母颠倒)核对后,改为本库的技能名(scripts/lpc_fix_skill_typo.py):matrial-cognize→martial-cognize(qiankun-danuoyi/nuoyi、qiankun-danuoyi/yi);qianzhu-wandu→qianzhu-wandushou(/kungfu/class/emei/zh、qianzhu-wandushou/tuxi);xuantian-wuji→xuantian-wujigong(liangyi-jian、taxue-wuhen);choushui-zhang→chousui-zhang(chousui-zhang);cuhixiao-jifa→chuixiao-jifa(xiaoao-jianghu);feilong--cuff→feilong-cuff(feilong-cuff/dan);hengshan-jianfa→hengshan-jian(/d/henshan/tianquan-hole);lenyue-shengong→lengyue-shengong(lengyue-shengong);xiawuxiang→xiaowuxiang(jidao-shaqi);yaowang-mishu→yaowang-miaoshu(yaowang-miaoshu)。

技能名拼错:技能文件里拼错自己的名字(2026-10-15,KB 06 §7.249)

技能自己的文件(或它的绝招目录)里,query_skill() 读的是差一个字母的自己的名字,永远得 0:“基本功夫要高过本技能”之类的限制从不生效,按等级取招、计算伤害的那一项一直是 0。改为本技能的名字(scripts/lpc_fix_skill_typo.py 的 self 判据):xuanyuan-arror→xuanyuan-arrow(xuanyuan-arrow)。

密宗猴拳「震」最重的一档报错(2026-10-15)

/kungfu/skill/mizong-houquan/zhen 按伤害分档写描述:前几档都是 pmsg = ...,最后一档(伤害 ≥ 80,也就是最重的一击)却写成 pmsg += ...,而 pmsg 没有初值,本驱动里 0 += 字符串 直接报错,这一招打得最狠时反而中断。改为 pmsg = ...。

地图数据库 mapdb 的空档防护(2026-10-15,KB 06 §7.250)

genmap 第一次载入时 if (!restore()) save(); 会在地图建好之前写出只有文件头的 data/npc/map.o;mapdb 读它成功后 map 为 0,所有查询都对 0 取下标(nitan170911、shenzhou 等 10 个库就带着这样的文件)。没有 map.o 时 query_random_room_path() 又对空数组取下标。mapdb(/clone/obj/mapdb.lpc)加一个 restore():读档后 map 不是映射就重置为 ([]);地图为空时 query_random_room_path() 返回 0。

临济庄「幽冥桩」的两处笔误(2026-10-15,KB 06 §7.245、§7.250)

linji-zhuang/youming:killer->querry(...)、me->querry(...) 调用的是不存在的函数,永远得 0,恶人比值只按 PK 数算,改为 query;visit_room() 先拿 range 作判断、后从参数里取 range,判断时它还是 0,效果遇到没载入的房间就停,两行取值挪到前面。

柔云步法「柔云纵」有时报错(2026-10-15,KB 06 §7.251)

rouyun-steps/zong 随机把人送到三个房间之一:精、精力已经扣了,“乘云而去了”也说了,送不过去时人留在战斗里并报错。第三个是巫师的测试房 /u/deaner/test1,库里没有;改为 /d/city/guangchang(hy、jym 等库原本就用这个)。

秋林拾叶「随风而舞」成功时报错(2026-10-15,KB 06 §7.252)

qiulin-shiye/wu 把房间的出口映射当数组:mapping *obb = query("exits", env),成功时 me->move(obb[0]),映射里没有键 0,移动到 0 报错(没有出口的房间则是对 0 取下标)。改为 yanhuangwuhun 一系的写法:从 values(exits) 里随机选一个出口。

call_out(0) nest level : 10(2026-10-15,KB 03 §5.4)

这个值来自驱动测试套件的配置(testsuite/etc/config.test)。驱动统计的是同一个 gametick(本库 100 毫秒)里全游戏的 call_out(0) 总数,不是一条链的深度,超过 10 个就报 Nesting call_out(0) level limit exceeded:多段绝招(如华山剑法「剑芒」的 call_out("perform2", 0, ...))在人多时会断掉。原版跑在 MudOS 上,没有这个限制;改为驱动默认值 1000。

用兵器的绝招空手施展时报错(内联写法)(2026-10-15,KB 06 §7.246)

绝招不存兵器变量,直接写 (me->query_temp("weapon"))->query("name")(或在拒绝判断里写 (weapon = me->query_temp("weapon"))->query("skill_type")),之前又没检查兵器:空手 perform 时报 Bad argument 1 to EFUN call_other(),内力等往往已经扣掉。在第一次用到它的那条语句前补上“你必须手持兵器才能使用这一招”的拒绝(scripts/lpc_fix_perform_weapon.py):shenlong-jian/anxi。

存档读不出来:字符串里的换行、空行(2026-10-15,KB 03 §4.4)

help/newsd.o:字符串里有原样的换行(save_object() 存成 \r,restore_object() 逐行读,原样的换行让字符串收不了尾),或有空行(Illegal file format),读档的对象(留言板、NPC、玩家)从空白开始。换行改写成 \r、删去空行(scripts/lpc_save_encoding.py --from-raw),驱动核对全部能读回(scripts/lpc_save_restore_check.py)。

移进不存在的巫师房间(2026-10-15,KB 06 §7.251)

用完的物品、要“离开”的 NPC、掷在空中的兵器被移进巫师的工作室(/u/aln/、/u/smallfish/),本库没有这些房间。本库的 move() 用 call_other(dest, "???") 载入目的地,找不到文件就报 call_other() couldn't find object,后面的语句都不执行:药(人参,clone/drug/cb_renshen)吃下后药效已经生效,随后报错:call_out("destroy", 31) 没有排上,药一直留在身上,药效过去后可以再吃;婚礼最后一步(d/suzhou/npc/meipo 的 last_marry)先把月老送走,在这里报错:新人不会被送上花轿去迎宾楼,喜宴没有了。2 个文件改为移进本库的虚空房间 VOID_OB(/clone/misc/void),与原作把它们“停放”在巫师房间的用意相同(haiyang2 的维护者把月老送进 /d/city/empty,也是同样的做法)。

物品清单把名字的第一个字吃掉(2026-10-18,KB 07 §8.1)

inventory、房间的物品列表(feature/move)和部分 look 把同名物品合并计数,键是 query("unit") + short(),只有一件时按 key[2..] 去掉量词。量词是一个汉字,在原来的 GBK 里占 2 字节,[2..] 正好去掉量词;转成 UTF-8 以后一个汉字只占一个下标,这一刀把名字的第一个字也切掉了(「把长剑」只显示「剑」,带颜色的名字切掉的是转义序列开头)。改为 [1..](scripts/lpc_fix_unit_cut.py):d/city2/look.lpc。

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 载入不了,刷出它的房间也少一个人。本库有 3 个对象设了本库没有文件的武功(15 个名字,最多的是 xinyuegong、taizuquan、liangyi-dao)。三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。抽样载入这些对象:3 个里 3 个通过。(scripts/lpc_guard_set_skill.py)

武功名拼错的 NPC(2026-10-19,KB 06 §7.258)

set_skill("kunlun-shnfa", …)、map_skill("dodge", "shaolin-shnfa") 这样的调用把武功名拼错了:本库没有这个武功文件,有的是拼对的那个。以前这种 NPC 在 create() 里报 No such skill 载入不了;武功缺失时跳过之后它们能载入,可是少了那门武功(set_skill 被跳过,map_skill 映射不到,门派弟子用普通轻功/拳脚)。按核对过的对照表改正,共 25 处、13 个文件:yunu-xinjing→yunv-xinjing(7 处)、jinyin-baiguzhao→jiuyin-baiguzhao(4 处)、changquana→changquan(4 处)、tiaji-jian→taiji-jian(2 处)、shenkongxing→shenkong-xing(2 处)、taiji-shenfong→taiji-shengong(1 处)、shaolin-shnfa→shaolin-shenfa(1 处)、yan-fly→yanfly(1 处)等共 11 个名字。(scripts/lpc_fix_skill_names.py)

set_name 的 id 参数传成字符串(2026-10-19,KB 06 §7.260)

set_name("海口大碗", "bowl"); 把物品的 id 写成了字符串,本库的 set_name(string name, string *id) 要的是数组,驱动在编译时报 Bad type for argument 2 of set_name ( string * vs string ),整个物件载入不了;旧驱动不检查参数类型,字符串里照样找得到这个词,物品能用 id 称呼。改成 ({ "bowl" }),共 1 个文件:/d/baituo/npc/obj/bowl.lpc。(scripts/lpc_fix_set_name_id.py)

die_reason 的参数个数(2026-10-19,KB 04 §6.10)

/kungfu/condition/damo_luanqi.lpc、/kungfu/condition/damo_shangshen.lpc、/kungfu/condition/damo_zheyuan.lpc 里的 string die_reason() 覆盖了基类的 die_reason(string name),驱动报 Number of arguments to 'die_reason' disagrees with previous definition。补上 varargs,调用处不用改;改后与 HEAD 逐个物件对比载入,没有退步。

出口指向不存在的房间(2026-10-19,KB 06 §7.257)

下列房间的出口写的文件不存在,而对面恰好只有一个房间的出口指回本房间,所以按对面的房间改正(笔误、大小写、路径少了一级):

改后房间照常载入(与 HEAD 逐个对比,没有退步),出口现在指向存在的房间。

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.274)

scripts/lpc_fix_swallowed_statement.py 把换行放回去。逐个载入与 HEAD 对比,没有退步。

从 work/ 里删掉 tar 的 PaxHeader/ 记录(2026-10-19,KB 02 §2.2)

压缩包的制作者把 tar 的 pax 扩展头记录(49 path = ...、20 ctime = ...,每个不超过 262 字节)当成文件打进了包里,放在 PaxHeader/ 目录(有的嵌套两层)。转换时 .c 结尾的被改名成 .lpc,在扫描里成了编译不过的“物件”。这些是元数据,不是 mudlib 内容:82 个文件从 work/ 删除(raw/ 里仍然有)。

x == "a" || "b":后面的字符串常量永远为真(2026-10-19,KB 06 §7.281)

if (arg == "yuyou" || "yu you") 里的 "yu you" 是非空字符串,永远为真,整个条件对任何 arg 都成立;作者要的是每个字符串都和变量比较。具体是:吃任何东西(eat 后面不管写什么)都会吃掉紫草;valid_combo() 对任何组合都返回真。在每个字符串前补上变量和运算符(scripts/lpc_fix_literal_or.py;forrest2.lpc 是 dir == "north" && "south",手工改成 (dir == "north" || dir == "south") && ...):/clone/medicine/zicao.lpc(1 处)、/kungfu/skill/meinv-quan.lpc(1 处)。

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)。具体是:valid_learn() 对任何性格都拒绝。改动:/adm/daemons/ward.lpc(1 处)、/kungfu/skill/qiankundanuoyi.lpc(1 处)、/kungfu/skill/zhonghua-aojue.lpc(1 处)。

member_array('/', str) 改为 strsrch(str, "/"):字符字面量的 member_array 按字节搜索(2026-10-20,KB 06 §7.283)

member_array('/', str) 里的 '/' 是整数,驱动按 UTF-8 字节搜索,返回字节偏移(带第三个参数时起点也是字节);最新的 FluffOS(923cdbc0)在字符串里有非 ASCII 文字时,对每个调用点警告一次(Warning: member_array(int, string) searches BYTES)。而 str[0..idx-1] 按字符切,中文字符串会在错误的位置截断。改为 strsrch(str, "/")(找不到同样返回 -1,按字符计数;scripts/lpc_fix_member_array_char.py,JSON 编码器等少数几处手改)。改动:/adm/daemons/vrm_server.lpc(1 处)、/cmds/arch/sameip.lpc(1 处)、/cmds/usr/mazemap.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,共 5 处)。改动:/adm/daemons/competed.lpc(2 处)、/adm/daemons/xinruid.lpc(2 处)、/cmds/wiz/roomlong.lpc(1 处)。

socket / dumpsocket 指令:dump_socket_status() 驱动里没有(2026-10-20,KB 06 §7.291)

这些巫师指令 write(dump_socket_status());,MudOS 的 efun 在 FluffOS 里不存在,指令编译不过。在模拟 efun 文件里补了 dump_socket_status():用 socket_status() 排出同样的表(没有 sockets 包的构建,比如 WASM,只有表头;scripts/lpc_add_sefun.py dump_socket_status)。

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

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

目录读出后的后缀截取宽度(2026-10-20,KB 06 §7.296)

.c 改名成 .lpc 之后,get_dir("*.lpc") 读出的文件名还按两个字符截取($1[0..<3]):baguadao.lpc 变成 baguadao.l,用它拼出的路径加载不到对象(故事、战场、事件、帮助、自造物品数据、召唤列表等)。改成 [0..<5](scripts/lpc_fix_getdir_strip.py)。改动:/cmds/adm/refsum.lpc(2 处)。

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) ||)。改动:/inherit/char/npc.lpc(共 1 个文件)。

blockade / recover:data/keep 不存在,解封之后世界仍然锁着(2026-10-21,KB 06 §7.340)

管理员扫描里 blockade 报 Error copying file from /data/user/f/fluffos.o to /data/keep/fluffos, Error: No such file or directory,随后的 recover 在第一个 cp() 上死于 */data/keep/fluffos: lstat failed,来不及执行最后的 LOGIN_D->set_madlock(0):封锁解不开。data/keep/ 是运行时目录,git 克隆里没有空目录。blockade 在第一次写之前 if (file_size("/data/keep") != -2) mkdir("/data/keep");(mkdir() 失败答 0,不抛错),recover 对没有保存档的玩家 if (file_size(src) < 0) continue;,其余照常恢复、照常解锁(scripts/lpc_fix_keep_dir.py,hy5 上实测:没有保存档时 recover 答“时空解封完毕”,blockade 建出 data/keep 并保存,recover 取回并删除)。改动(1 个文件):/cmds/arch/recovermud.lpc。

拼错的 call_out() / input_to() 目标(KB 06 §7.349,2026-10-21)

计时器或输入提示给驱动的函数名和文件里定义的差一个字母;驱动对不存在的函数静默不理,所以对应的计时从来没有触发过。调用和配套的 remove_call_out() 改成已定义的拼法:ruanweijia / yuxiao:return_to_zhang → return_to_huang(携带者在战斗时的重试从不发生,宝物一直留在玩家手里);张无忌:destrory → destroy。改动:/clone/lonely/ruanweijia.lpc、/clone/lonely/yuxiao.lpc、/kungfu/class/mingjiao/zhangwuji.lpc。

注册到不存在函数的 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.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 25 条注册(19 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/adm/npc/biwu1.lpc(get_weapon)、/adm/npc/biwu2.lpc(get_weapon)、/adm/npc/biwu3.lpc(get_weapon)、/adm/npc/biwu4.lpc(get_weapon)、/adm/npc/referee.lpc(do_halt)、/clone/drug/guiyuan.lpc(do_pour)、/clone/drug/huiyang-san.lpc(do_pour)、/clone/drug/shedan.lpc(do_pour)、/clone/drug/yangxin.lpc(do_pour)、/clone/drug/yulu-wan.lpc(do_pour) 等共 19 个文件。最多的函数名:do_pour 7 处、get_weapon 4 处、do_bcun 3 处、do_bqu 3 处、do_halt 2 处。

缺失物品的 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 都被跳过。7 个文件里 12 处(new)改成 { object new_ob = new(路径); if (new_ob) new_ob->move(...); }(scripts/lpc_guard_carry_object.py --new:路径按这个文件自己看到的宏解析,宏名冲突、虚拟对象路径一律跳过)。 对这 7 个改动文件做加载检查(HEAD 与工作区对比):HEAD 能加载 7 个,现在 7 个(新增可加载 0 个,回归 0 个)。

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

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

(: 函数 :) 指针指向对象自己没有定义的函数(2026-10-22,KB 06 §7.355)

item_desc / inquiry / chat_msg_combat 里的 (: 函数名 :) 指向本对象(含包含文件、继承的父类)没有函数体的函数:编译不报错,用到时驱动报 *Undefined lfun pointer called: 函数名(look 某物、ask NPC about 话题 直接出错,战斗 / 闲聊里的指针在 NPC 的 chat() 里出错,这一拍心跳的后半段被跳过)。scripts/lpc_scan_callout_targets.py --kinds=lfun。 还剩 3 个函数没有补(item_desc 3):原型还在函数体丢了、或者根本没写过,没有可以直接补回的捐赠(别的库同一路径的文件里没有这个函数,或者它还要用本库没有的辅助函数、签名不同),不编造:/d/yihua/lianhuachi.lpc:look_CAI、/d/yihua/lianhuachi1.lpc:look_CAI、/d/yihua/lianhuachi2.lpc:look_CAI。

运行期目录 log/nosave 不存在,写日志的 write_file() 直接报错(2026-10-22,KB 03 §4.3 补充)

这个库的 77 个文件里有 136 处 write_file("/log/nosave/...") / log_file("nosave/...")(static 改 nosave 时连字符串里的日志路径一起改了,原来是 log/static/):nosave/using、nosave/party_quest、nosave/item 等。压缩包和站点包里没有这个目录,write_file() 在目录不存在时抛 *Wrong permissions for opening file /log/nosave/... for append. "No such file or directory",调用它的函数(杀人记录、重启记录、call 日志……)当场中断。把 log/nosave/guilei 记进 scripts/wasm_keep_dirs.txt(站点和 pristine 树都按它建空目录);没有把字符串改回 static。证明(scripts/lpcc_eval.py,catch(write_file("/log/nosave/ZZTEST", ...)),fqyy2 / hy5 / bmxkx2001 的新进程):补目录前报上面的错误,补后写成功。

(: 函数名 :) 指针指向本库没有的函数:只含这个指针的行加了注释(2026-10-22,KB 06 §7.355 补充)

inquiry / item_desc / chat_msg* 里的 (: 函数名 :) 指向本对象(含包含文件、继承的父类)没有函数体的函数:用到时(ask、look、NPC 闲聊)驱动抛 *Undefined lfun pointer called: 函数名,这一拍心跳的后半段被跳过。这些函数没有可以补回的来源(别的库同一路径的文件里没有,或者它还要用本库没有的东西),所以 3 个文件里 3 行只含这个指针的行("CAI": (: look_CAI :),)加了注释,原文保留并写明原因,这一项当作没有这个话题 / 物品 / 闲聊条目(scripts/lpc_fix_dangling_lfun.py;#include 的文件在库里缺失的对象、指针不在整行上的写法没动)。 前提(function_exists())和加载检查(HEAD 与工作区对比,本库改动的 .lpc 文件)的结果见 KB 06 §7.355:没有一个回归。

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

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

本库 21 处(14 个文件):拼名字的赋值 10 处、与汉字常量比较 10 处、to_chinese()[..] 1 处。例:name[2..3] → name[1..1](/kungfu/class/emei/fengling.lpc:134);name[0..1] → name[0..0](/kungfu/class/emei/tobebonze.h:41);chk[0..1] == "你" → chk[0..0] == "你"(/adm/npc/ganjiang.lpc:556)。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,不要重跑。

长度检查按字节写,旁边的提示按汉字写:14 处(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 你好吗)。本库 14 处(7 个文件):if ((strlen(name) < 4) || (strlen(name) > 12)) {(/adm/npc/luban.lpc:1774);if ((strlen(mission) < 4) || (strlen(mission) > 20)) {(/cmds/adm/mission.lpc:57)。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 区间 7 个文件;channeld 同盟 / 门派标题 1 个文件。证明(scripts/lpcc_eval.py):sjplii 的 check_legal_name 在 HEAD 接受「张a」、改后拒绝,两边都接受「张三」;xkx100 的 clubpoem->mixup("离离原上草") 得到「离离原草上」(真交换汉字);nitan_san 两字同盟名得到 【 兄 弟 】。关键对象加载通过。

血条 / 表情标题按字节算显示宽度(2026-10-23,KB 06 §7.365)

老档把汉字当两字节:bar_string[0..(val * N / max) * 2 - 1] 把「格数」换成字节下标,半格血条在本驱动(按字符算区间)上会整条画满;巫师表情 edemote 的 repeat_string("─", 13 - (strlen(emotion) / 2)) 也会少减汉字宽度。scripts/lpc_fix_display_pad.py:去掉 CJK 血条 / 空白条上的 * 2(jings/qis 赋值和 blank_string3[0..(23-a)] 的 23→11 一并改),表情标题去掉 / 2;ASCII 空格的 none_string / empty_string 仍保留 * 2(一格两个空格)。本库 9 处(2 个文件)。证明(lpcc_eval.py hhsj):tribar_graph(6,9,12) 在 HEAD 画出整条 12 个■,改后为 6 个■+3 个□;麻将 strlen(tempB)/2 是两字符牌码,未改。

中文名 / 验证码仍按 GBK 取「两字节一字」(2026-10-23,KB 06 §7.373)

pname = pname[(random(sizeof(pname)) & 0xFFFE)..<1]; pname = pname[0..1]; 在按字符计下标时会取出两个汉字。scripts/lpc_fix_pname_slice.py 去掉掩码并把收尾改成 [0..0](pname/pname2/sec_name/str/str1)。本库 18 处。证明:nt1 生成名长度 2–4;HEAD 常见 3–5。

无参数时命令对 0 的调用(2026-10-27,KB 06 §7.336 Also)

巫师/管理员命令在通过权限门之后仍会把 0 交给 sscanf / read_file / find_living 等。lpc_fix_bare_arg.py 在首次使用参数前加上 if (!arg)(或 help(me) / GetHelp())。本库本批改动见该提交。

无参数时命令对 0 的调用(玩家/--all-dirs 残留,2026-10-27,KB 06 §7.336 Also)

无参数命令把 0 交给 present / sscanf / file_exists 等会运行时报错。lpc_fix_bare_arg.py --all-dirs 在首次使用参数前加守卫。多行 if (arg && !sscanf)(吐纳默认消耗)与已有 can_learn/get_object 守卫的误报已回退;本库本批改动见该提交。

yuanwai 孤儿 call_out announce_success(2026-10-27,KB 06 §7.349 Also)

d/changan/npc/yuanwai.lpc(及 aoxiangtianji 的 d/city 副本)在 check_daughter 里 call_out("announce_success", …),但本文件从未定义该函数;同系也无实现。到点会 *Undefined function*。cry_daughter 已完成认亲与赠书,删除这对 remove_call_out/call_out。证明:扫描 HIT call_out→announce_success 且无定义;改后残渣 0。

深度功能测试(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… 行。