The Smiling, Proud Wanderer II (Xiao Ao Jiang Hu II)

✅ 可玩

笑傲江湖 II

xajh2

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

▶ 开始游玩 · Play Now

游戏登录画面自称"虚拟人生",属于"XO mudlib"引擎家族(源自 TMI-2/ES2,由 Falcon 于1996年重写),与本项目另收录的《笑傲江湖迷你版》《XO最终版1.2》同源但独立演化、开站更晚的一个站点快照(站内公告显示自2003年4月1日开站);虽然打着《笑傲江湖》的旗号,继承自共同 XO 世界模板的门派体系其实是金庸多部小说的大杂烩——《倚天屠龙记》里的少林、武当、峨嵋、昆仑、崆峒、华山六大派,与《天龙八部》大理段氏皇宫、神农帮、无量剑派并存,此外还有丐帮及其对立的情报机构"一品堂";角色成长分拳脚、轻功、剑术、刀法等相互独立的修为体系。新建角色对同一 IP 地址限流以防刷屏,退出游戏(quit)需先玩到10点经验才能存档,均为刻意的反滥用设计而非缺陷。

English

Branded at login as "Virtual Life," this is a member of the "XO mudlib" engine family (descended from TMI-2/ES2, rewritten by "Falcon" in 1996) and a later, independently-evolved snapshot of the same bloodline as this collection's Xiao Ao Jiang Hu XO and XO Final 1.2 — in-game notices show it opened April 1, 2003. Despite the Smiling, Proud Wanderer branding, the actual sect roster inherited from the shared XO world template is a broad Jin Yong multi-novel mashup: the Six Great Sects of Heaven Sword and Dragon Saber (Shaolin, Wudang, Emei, Kunlun, Kongdong, Huashan) sit alongside Demi-Gods and Semi-Devils' Dali/Duan-clan palace, the Shennong Gang, and the Wuliang Sword sect, plus the Beggars' Sect and its rival intelligence service, the Yipin Hall. Character growth runs along separate fist/palm, qinggong, sword, and blade skill tracks. New-account creation is IP-rate-limited to curb spam, and quitting before earning 10 experience points forfeits the save — both deliberate anti-flood design, not bugs.

README

内容亮点

在线试玩

https://mudlibs.fluffos.info/xajh2/

管理员账号 / Admin account

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

本地运行

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

游戏端口:40068。

NOTES · 移植与修复记录

xajh2 — 笑傲江湖 (XO/TMI-2/ES2 lineage, NOT the nitan family)

Archive: 笑傲江湖II.rar. Port: 40068. Status: done (boots clean, full registration with a real Chinese name works end-to-end, and post-login commands look/score both produce real output).

Lineage check (done first, per instructions) — related to xo/xo_final, NOT nitan

Despite sharing the "笑傲江湖" (Laughing in the Wind) title with nitan6 (archive #22), this is a completely different codebase — confirmed by evidence, not assumed either way:

Conclusion: same lineage family as xo/xo_final (TMI-2/ES2/Falcon "XO mudlib"), a distinct build/snapshot — not a duplicate, not nitan-family. xo's §4 fix was ported directly (proven fix, same file shape); the §15/ §15a-c/§15v nitan-specific fixes do not apply here at all.

What this is (per the archive's own README, 说明.txt)

Actual game name is 笑傲江湖 (config.xo's name field says "笑傲江湖 迷你版" — a stale copy-paste leftover from the mini-edition config, not this build's real name, same class of leftover documented for dtsl2 in TODO.md). Per 说明.txt: official address xo.27net.net:4000, default wizard account locker/000000 (present in secure/etc/wizlist). Used the archive's own working title "笑傲江湖II" for the slug/config name since that's what distinguishes it from xo/xo_final in this project's naming.

Fixes applied (with why)

1. AGENTS.md §15h — is_chinese/IsLegalName GBK byte-range bug (proactive, confirmed present here too): - secure/sefun/chinese.lpc's is_chinese(str): strlen(str)>=2 && str[0]>160 && str[0]<255 (GBK lead-byte check) → strlen(str)>=1 && str[0]>=0x4e00 && str[0]<=0x9fff (CJK codepoint range, UTF-8 native). - system/daemon/band.lpc's IsLegalName(name): length bound i<4 || i>10 (4-10 *bytes* = 2-5 GBK chars, matching the message "只能是 2 到 5 个中文字") → i<2 || i>5 (2-5 *characters*); and the per-character loop's i % 2 == 0 && !is_chinese(name[i..<0]) (byte lead-position gate) → dropped the i % 2 == 0 && entirely (every UTF-8 index is already one full character). - Verified end-to-end: sent the real Chinese name 秦风 through registration and it was accepted, advancing to the password prompt — not just "reached the prompt".

2. AGENTS.md §4 — master.lpc lazy-security-daemon reentrancy guard (ported directly from xo's proven fix, same file shape — secure/daemon/master.lpc's valid_write/valid_read already wrapped load_object(SECURITY_D) in catch() but had no re-entrancy guard and no find_object() fast-path check first): added private nosave int loading_security_d; and the same find_object-first / guarded-load / degrade-to-default-on-reentry shape used in libs/xo/work/secure/daemon/master.lpc.

3. New bug (not previously cataloged): combatd.lpc's switch/case with every real case commented out, leaving a bare default: — this driver requires at least one real case statement in a switch (error: need case statements in switch/case, not just default:). Since every branch except default was already dead code, replaced the whole switch with the default branch's statement directly (no restored content, just removed the now-meaningless switch wrapper). This is a preloaded daemon (combatd is in etc/preload), so this was a genuine boot-blocking compile error, not a dormant one.

4. New bug (not previously cataloged, high-impact): this driver build has __OLD_ED__ defined, so the "new" ed_start/ed_cmd/ query_ed_mode efuns this codebase's editor feature was written against simply don't exist at all (core.spec's #ifdef OLD_ED / #else split — only one side is ever compiled in). This broke TWO files: - system/feature/user/editor.lpc (the edit()/in-game-editor feature, inherited straight into /clone/user/user.lpc, the player body class itself) — failed to compile at all, which silently made make_body() return a broken/uncompilable object during registration with zero visible error (same failure shape as AGENTS.md §15b's "player-body class object itself failing to compile", new root cause). Fixed by rewriting edit() to call the OLD_ED ed(file, exit_fn_name, restricted) efun instead, with a new editor_exit_fn() callback replacing the old manual query_ed_mode()==-1 completion-detection. Confirmed via ~/src/fluffos/src/comm.cc that under __OLD_ED__ the driver's own C-level input loop (if (ip->ed_buffer) { ed_cmd(...); goto exit; }) intercepts every line directly while editing, bypassing input_to()/process_input()/write_prompt() entirely — so the old manual per-keystroke ed_cmd() forwarding in process_input() was unreachable dead code under this driver, not something to reimplement; simplified to a harmless passthrough. - system/feature/user/message.lpc's write_prompt() had the same query_ed_mode() dependency for customizing the in-editor prompt — same root cause, same fix rationale (dead code under __OLD_ED__, since print_prompt() in comm.cc also short-circuits straight to ip->prompt for ip->ed_buffer-active connections, skipping APPLY_WRITE_PROMPT). Removed the branch. - This is likely to recur on other libs — worth a new AGENTS.md catalog entry (§15af-shaped): any lib using ed_start/ed_cmd/ query_ed_mode needs this same fix, and the tell is a compile error in whatever file is inherited into the player body class, which then manifests as registration silently failing right after character creation with no error shown anywhere.

5. New bug (not previously cataloged, the hardest one to diagnose in this pass): the /log/nosave/ directory was entirely missing from the shipped archive (classic "RAR/zip drops empty dirs", same class as several prior libs) — used by log_file() targets like PPL_USAGE_LOG/WIZ_USAGE_LOG/COMMAND_LIMIT_LOG (all "nosave/..."-prefixed in include/globals.h). An uncaught write_file() error to that missing path, inside secure/sefun/file.lpc's log_file() simul_efun, silently aborted the entire get_gender() → log_login() → init_new_player() → enter_world() call chain right after gender selection — with zero visible error anywhere: not in debug.log, not shown to the connecting player. Bisected with temporary efun::write() checkpoints (removed after diagnosis, per AGENTS.md's established §8c/§15d technique) down to the exact statement, then discovered the *actual* error WAS being logged — just to the mudlib's own /log/runtime/secure file (via master.lpc's custom error_handler() apply), not debug.log. It was never shown to the player because error_handler() only calls message("error", ...) when this_player() is truthy, and at that specific point in the pre-exec() registration flow it apparently wasn't. New diagnostic lesson, worth cataloging: when a registration/input_to flow silently stalls at one exact step with *nothing* in debug.log and nothing shown to the player, check the mudlib's own log/runtime/* and log/catch files (if it has a custom error_handler() master apply) before assuming there's no error at all — the mudlib's own error logging can be strictly more complete than what reaches the terminal. Fix: created work/log/nosave/ (mkdir), and wrapped log_file()'s write_file() call in catch() as defense-in-depth (§15e-style graceful degradation — a failed bookkeeping log write should never be allowed to abort character creation).

6. §15i-shaped bug (comment eats the following code), recurring in two sibling files: system/std/char/task.lpc and system/std/char/special_quest.lpc both had // 现在先共用一个quest库 if ( random( class_score ) < 50 ) — the Chinese comment and the real if statement are on the *same physical line*, so // swallows the entire if, leaving a dangling else if/else chain with no matching if (error: syntax error, unexpected L_ELSE). Found via the exact same distinctive comment text in both files (one shared bug, not two) — split the comment onto its own line in both.

7. clone/user/immortal.lpc: string array arrCmd = explode(cmd,";") — TYPE array NAME is not valid array-declaration syntax on this driver (distinct from AGENTS.md §15f's *bare* array — this is a *different* two-word "TYPE array" spelling, presumably a synonym this codebase's original target driver accepted). Fixed to string *arrCmd. This is the wizard-only multi-command (;-separated) batching feature in the wizard player-body class.

8. system/daemon/poisond.lpc: called set()/restore() (F_SAVE/F_DBASE methods) but never inherited either — a genuine pre-existing omission (every sibling daemon, e.g. emoted.lpc, does inherit F_SAVE;). Added inherit F_SAVE;. Not fully fixed even after this — lpcc still reports a (silent, no explicit error text) failure to load, most likely restore_object() erroring on nonexistent save data at create() time. Not pursued further: poisond is not in etc/preload and nothing else in the lib call_others into it — confirmed dead/unreachable code, so this doesn't affect boot or the verified registration/gameplay path.

Confirmed NOT needed (checked by reading the actual source)

New bug classes found here, worth AGENTS.md entries (flagged for the

main session to catalog)

1. A switch with every real case label commented out, leaving a bare default:, is a hard compile error on this driver (combatd.lpc). 2. __OLD_ED__ vs the "new" ed_start/ed_cmd/query_ed_mode API — this driver build only has ONE of the two editor APIs compiled in (__OLD_ED__ is defined), so any lib written against the other one fails to compile wherever it's used; if that's inherited into the player body class, registration silently breaks right after gender/ character creation with zero visible error. Likely to recur. 3. An uncaught write_file()/log_file() error to a missing directory during a pre-exec() input_to callback chain can abort the whole chain with zero visible error in debug.log or to the player — the mudlib's own custom error_handler() DOES log it, but only to its own log/runtime/<X> file, gated on this_player() for player visibility. New diagnostic lesson: check the mudlib's own runtime/catch log files, not just debug.log, when a flow silently stalls. 4. TYPE array NAME (two words, not AGENTS.md §15f's bare array) is ANOTHER old-MudOS-dialect array-declaration spelling this driver doesn't support — found extensively (12+ instances) in a dead legacy file (system/daemon/xoban.lpc, superseded by band.lpc per its own header comment) and once in a live file (immortal.lpc, fixed). 5. §15i's "comment eats following code" pattern recurred in a new shape: the comment and the swallowed code are on the *same* physical line (not the comment eating the *next* line) — found identically in two sibling files via one shared distinctive comment string.

Interactive test result (full transcript outcome)

Registration flow, in ONE continuous mudclient-style connection: new → qinfeng (English id) → y (confirm) → 秦风 (real Chinese name — accepted, not rejected) → 123456/123456 (password + confirm) → [email protected] (email) → m (gender) → lands in the game world (welcome banner "温故知新" shown).

Post-login commands (§15ae standing policy, tested after registration completed, not just at the prompt):

This satisfies AGENTS.md §15ae's standing policy: registration was verified with a REAL Chinese name in one continuous connection, AND at least one ordinary post-login command was confirmed to produce real output (not the generic fail message) after registration completed.

lpcc sweep

4344 files: 4242 pass / 102 fail (97.65%) before the additional fixes below; verified 3 of the 4 newly-fixed files now individually PASS (task.lpc, special_quest.lpc, immortal.lpc; poisond.lpc still fails for the unrelated reason noted above) — full sweep not re-run a second time given the remaining failures are already fully triaged below and the net gain would be ~3-4 files out of 102.

Failure categories (triaged, not blindly re-swept):

Directories created (archive shipped without them — zip/rar drops

empty dirs)

Re-verification pass (2026-07-23)

Clean boot re-confirmed (zero fatal errors). First interactive attempt hit AGENTS.md §15j head-on: a new registration sent from a SECOND, separate mudclient.py connection (after an earlier same-session attempt with an invalid id had already ticked BAN_D->IsTimeAllowed()'s per-IP throttle) produced literally zero server response to new — logind.lpc's die() has its diagnostic write() commented out, so the rejection is silent, indistinguishable at a glance from a hang/crash. Confirmed via a raw socket probe (send new, wait 8s, zero bytes back) and a clean debug.log (no errors at all) that the driver itself was healthy — this is the documented throttle behavior, not a regression. Fixed by doing the entire registration + look/score/quit sequence in one single continuous mudclient.py connection (id xoahmount, name 秦山), per §15j's own recommended workaround, which worked cleanly on the first try: real Chinese name accepted, entered the world (hit the same known, previously-documented /d/place/newbie/start missing-room content gap, gracefully degrading to void exactly as before), score rendered the full character sheet, and quit showed the real "10-point save gate" text. Zero genuine runtime errors in debug.log. No code changes needed — this pass was a clean re-confirmation.

Port / process hygiene

Port 40068 (next free after 40066-40067, reserved for archives #72-73). Driver was started/stopped many times during diagnosis (each source edit requires a fresh process — LPC files don't hot-reload); every kill used the exact PID from that launch's own $!/ps -ef output, never a broad pkill pattern (multiple *other* agents' driver processes were confirmed running concurrently throughout this session, e.g. for shenzhou, xajhzcjh, xajhxo — none were touched). No driver process left running at the end of this pass.

Driver rebuild / formatter / WASM pass (2026-07-23)

WASM-enablement pass (2026-07-24)

Standard four-change pass (AGENTS.md §1.3b/§1.3e/§1.5). Gates patched:

1. Loopback always allowed — system/daemon/band.lpc: added IsLocalSite(site) helper (127.0.0.1 / leading 127. / empty/non-string / malformed non-dotted-quad = WASM garbage), and short-circuited: - IsBannedSite() (called from logind.lpc StartLogon() line ~59) → local always returns 0 (not banned); - IsMultiLogin() (called at world entry for peaceful residents, logind.lpc line ~506) → local always returns 0; - IsTimeAllowed() (the 3-minute per-IP new-registration throttle whose rejection path is a SILENT disconnect, logind.lpc line ~142) → local always returns 1 (allowed). Verified live: two fresh registrations from 127.0.0.1 within 3 minutes both succeeded. 2. Uptime startup gate — the #ifdef LOGIN_DELAY startup-grace block in StartLogon() is dead (LOGIN_DELAY is defined nowhere); the uptime() < 10 check at line ~92 is purely cosmetic banner text ("刚启动" vs "已经运行了"), not a gate. Nothing to bypass. 3. KEPT (game design): the "must play 10 minutes before first save" quit/save gate (cmds/comm/quit.lpc / save.lpc, CAN_SAVE_LIMIT_TIME) — wizardp is exempt, so fluffos saves normally. 4. Admin account seeded — id fluffos, pw Mud@2026, name 浮浮, registered via the real flow (new → id → y → Chinese name → password x2 → email → gender m). Granted (admin) via secure/etc/wizlist. Verified update /system/daemon/band recompiles successfully.

Save files (untracked, NOT gitignored — orchestrator must git add):

(work/data/log/f/fluffos is a runtime per-char log, matched by the repo's **/log gitignore pattern — leave ignored.)

Retest: fresh normal registration (id qinfxa, 秦风) works end-to-end, score renders, quit-with-nosave gate fires as designed; fluffos login + wizard update works; zero new errors in debug.log. Test char artifacts removed. Known pre-existing content gap unchanged: new players land in 太白楼 via the missing-/d/place/newbie/start fallback (documented in the earlier pass).

Fail-closed loopback retrofit (2026-07-24)

Security correction, applied retroactively. IsLocalSite() (item 1 above) originally treated an empty/non-string/malformed IP as "local" (fail-open) — a stopgap for a since-fixed WASM driver bug. Tightened to fail-closed:

int IsLocalSite(string site) {
  if (!stringp(site)) return 0;
  if (site == "127.0.0.1" || site == "::1") return 1;
  if (strlen(site) >= 4 && site[0..3] == "127.") return 1;
  return 0;
}

IsBannedSite(), IsMultiLogin(), and IsTimeAllowed() all delegate to this one helper, so no other call site needed a separate edit; an unparseable/empty IP now falls through to the normal (non-exempt) path in all three. Retested: fresh registration (id xajhgate, name 秦岭峰) still completes end-to-end via loopback (score renders correctly, lands in 太白楼 per the pre-existing content gap above, quit's 10-point save gate fires as designed — no save file created, nothing to clean up); fluffos login + update /system/daemon/band still succeeds (重新编译 /system/daemon/band.lpc:成功!). Zero new runtime errors from this change (the pre-existing hui_quest.lpc syntax-error lines seen in debug.log are an unrelated, unchanged content bug in /d/menpai/shaolin/npc3/hui_quest.lpc, not touched by this pass and not on the login/registration path).

深度功能测试 / Deep functional test (round two, 2026-07-25)

Full hands-on playthrough per AGENTS.md §10.7, one continuous mudclient.py session per major phase (multiple phases because prior findings required a fix + driver restart before continuing — each restart began a fresh phase, not a fresh probe). Read help/user/newbie first (行路篇/拜师学艺篇/交流篇 sections): confirmed the intended new-player loop is register → explore the local city → duilian/bihua for safe practice fights → qingjiao <师傅> <技能> to learn from an ordinary city NPC before ever committing to a sect.

Test character: id qinfeng, Chinese name 秦风, male, registered through the real flow (new → id → y → 秦风 → password ×2 → email → m). Landed via the /d/place/newbie/start fallback in either 高昌城中心 (Gaochang) or 小秦淮客寓 (Yangzhou, per select_start_room()'s random pick — see bug 3 below for why this now actually matters). Test character and its save files (data/user/q/qinfeng.o, data/login/q/qinfeng.o, data/log/q/qinfeng*) were removed after testing, per this project's usual convention (§1.5-seeded fluffos admin excepted). Restored one UNRELATED pre-existing tracked file at the same log path (data/log/q/qinfeng, part of the original shipped archive, coincidental id collision — verified via git log before touching it) after an overly-broad rm.

New bugs found and fixed

1. d/menpai/shaolin/npc3/hui_quest.lpc — swallowed-comment syntax error breaking the Shaolin quest-giver's compile (AGENTS.md §6.6). Line 183: // 现在先共用一个quest库 if ( random( class_score ) < 100 ) — the // comment swallows the if condition on the same physical line, leaving line 184's tag = ... unconditional and a dangling else if/else chain with no matching if (error: syntax error, unexpected L_ELSE). This file is inherited by d/menpai/shaolin/npc3/huijue.lpc — one of Shaolin's actual apprentice-recruiting master NPCs (attempt_apprentice()/ recruit_apprentice() both live there) — so the compile failure silently removed that specific recruiting master from the game entirely. Reproduced live: this compile error fired on the very first fresh registration in this session (via select_start_room()'s room-loading scan touching the Shaolin zone), confirmed in debug.log. Already spotted-but-explicitly-left-alone by the prior "fail-closed loopback retrofit" pass (2026-07-24, this same NOTES.md, "not touched by this pass"). Fix: split the comment onto its own line, restoring if (random(class_score) < 100) tag = .... Verified: lpcc now compiles both hui_quest.lpc and huijue.lpc clean (only the usual benign #pragma/nosave-class warnings), and a fresh boot produces zero hui_quest-related lines in debug.log (previously appeared on every single boot). Swept the whole lib for the same comment string and for the general "comment line immediately followed by a bare {" shape (Python scan of every .lpc file) — the only other 4 hits were legitimate (function signature already on the preceding line); no further instances.

2. system/skill/basic/kongshou.lpc — same bug class, breaking the default UNARMED COMBAT skill used by every character. Line 39: // 这个函数用来区别这种天生的技能与其他后天学习的技能int is_native_skill() followed by a bare { on the next line — identical shape to bug 1, just with the swallowed code being a whole function signature instead of an if. kongshou (inherit SKILL, query_xiuwei_type() → "unarmed") is the base bare-hands combat skill every fresh character starts with (no weapon, no learned skill needed) — error: syntax error, unexpected '{' meant this file has never successfully compiled on this driver. Found live via checklist item 3 (safe-sparring mechanism): bihua <某人> (per help newbie's 拜师学艺篇 — combat that auto-stops at 20% HP/stamina, and the command itself tops up the opponent to full HP/force first if they're already above 20%, i.e. the "mirrors the attacker" stat-copy shape AGENTS.md §10.7 point 3 describes) against 老先生 (a peaceful, non-hostile teacher NPC at d/city/gaochang/npc/teacher.lpc, whose base-class accept_fight() accepts a challenge from a full-health player specifically because its attitude is unset — hits the default: branch, not the "friendly" refusal case). 老先生 accepted ("既然小兄弟赐教,老头子只好奉陪"), combat began, and every single combat round from then on threw *call_other() couldn't find object '/system/skill/basic/kongshou'! — repeating on EVERY subsequent heartbeat-driven combat tick, spamming the error even into unrelated commands (score, i) typed afterward while combat was still "active." This is a severe, universal bug: unarmed combat (the default state for literally every player without a weapon) never actually resolves correctly. Fix: split the comment/declaration onto separate lines, same shape as bug 1. Verified via lpcc: the file now compiles clean (previously a hard syntax error). Re-tested live post-fix (fresh driver restart, reconnected as 秦风): a follow-up bihua attempt against the same NPC no longer produced the error — confirmed via debug.log staying clean through the exchange. (The original NPC/room the first accepted-bihua repro used, 老先生 in Gaochang, was not re-reachable in the exact same accepted-combat shape after the character's startroom anchored to a different city on a later login — see bug 3 — so the positive re-verification used the same NPC's *declined*-bihua path plus a clean lpcc recompile as corroborating evidence rather than re-triggering a second full accepted exchange; documented honestly, not silently presented as re-proven identically.) Swept the lib for the same shape: dodge.lpc/yeshou.lpc/ horsedodge.lpc/wuqi.lpc (siblings sharing the same is_native_skill() doc-comment text) all have the declaration and opening brace on the same line — not affected.

3. system/daemon/logind.lpc's enter_world() — the long-documented missing-/d/place/newbie/start content gap ALSO silently skips user->save()/ob->save() for every brand-new character (new finding, matches AGENTS.md §7.14's exact guard pattern). Every prior pass on this lib (see the "Interactive test result", "Re-verification pass", and "Driver rebuild" sections above) correctly identified that /d/place/newbie/start doesn't exist in this archive and documented the symptom as "gracefully degrades to void, not fixed (would require fabricating missing content, out of scope)" — true as far as it went, but incomplete: user->move("/d/place/newbie/start") throws an uncaught *call_other() couldn't find object error that aborts the rest of enter_world() outright, including user->save() and ob->save() near its tail (system/daemon/logind.lpc lines ~657-658). Concretely, this means a fresh registration on this lib never wrote any save data to disk until the next periodic 15-minute autosave (AUTO_SAVE_INTERVAL = 900s, system/feature/user/autosave.lpc) or a net-dead force-quit (NET_DEAD_TIMEOUT = 900s, same value, system/feature/user/interactive.lpc) fired — both bypass the separate, intentional "must play 10 real minutes before manual quit saves" retention gate (CAN_SAVE_LIMIT_TIME = 1800s, cmds/comm/quit.lpc/include/options.h) because they route through later_quit()'s set_temp("valid_quit", 1) shortcut, which unconditionally calls me->save(). Practical consequence, confirmed live: any brand-new player who disconnects uncleanly (the single most common real-world disconnect mode) within their first ~15 minutes has NO save file at all yet — and system/daemon/logind.lpc's get_id() determines "does this id exist" purely from file_size(ob->query_save_file() + __SAVE_EXTENSION__) (line 166), never consulting find_body(arg) for a live/net-dead in-memory body (that check only happens later, in get_passwd(), which is unreachable without first passing the disk-existence gate) — so such a player is told "对不起,这个id还没有登录过,请用new来起用这个id" ("this id has never logged in, please use new") on any reconnect attempt before the 15-minute mark, even though their body is still genuinely alive server-side. This reads exactly like account/data loss to a real user. (This specific "login flow doesn't check live state before disk" shape is new — it doesn't quite match any existing §7.20/§7.21 entry, which are both about a *found* net-dead body's location not being restored; here the body is never *found* at all because the existence check short-circuits on disk state first. Possibly worth a new catalog entry if seen again on a sibling — see draft below.)

Fix, matching AGENTS.md §7.14's own prescribed pattern ("Guard the move with load_object()/fallback to START_ROOM"): wrap the newbie-room move in catch(), falling back to the same startroom value the two lines above it already validated via catch(load_object(startroom)):

if (newbiep(user)) {
  if (catch(user->move("/d/place/newbie/start")))
    user->move(startroom);
} else
  user->move(startroom);

Verified live end-to-end: fresh registration (fresh driver boot, id qinfeng, 秦风) landed in a REAL room (城中心, Gaochang — one of the 5 start_rooms candidates) instead of /clone/misc/void, and data/user/q/qinfeng.o / data/login/q/qinfeng.o existed on disk immediately after registration completed (previously: nothing on disk at all until 15 real minutes had passed). debug.log stayed clean (only the same, now-harmless /log/catch-routed notice that the newbie room is missing — no longer fatal to the rest of the flow). The underlying content gap itself (no /d/place/newbie/start in this archive) is unchanged and still out of scope (§7.14/§13 — an archive gap, not fabricated). Player-visible side effect worth noting: since startroom is only ever persisted via the OTHER fallback branch (catch(load_object(startroom)) failing) and never by the success path this fix exercises, a newbiep() player's landing room is picked fresh by select_start_room() on every FULL login (not full reconnects — a net-dead reconnect correctly resumes the same room the body never actually left, verified in the reconnect testing below) until whatever in-game action clears newbiep(). This is pre-existing behavior common to any of the 5 start_rooms entries, not something this fix changed or introduced — documented as an observation, not touched (game-flow question, not a crash).

Checklist coverage

Process hygiene

Driver booted/restarted 4 times during this pass (each source fix requires a fresh process — confirmed via readlink -f /proc/<pid>/cwd before every kill that the PID being killed was this lib's own driver, never another concurrent agent's; several other libs' drivers were confirmed running throughout, e.g. dtsl2, fy2qh, fy3dz, longyunmeng, shenmo, jqxz2008std, hymud — none touched). No driver process left running at the end of this pass. A background sleep+reconnect sequence intended for the real-wait net-dead check (item above) was armed then explicitly torn down mid-pass per a time-budget correction — no dangling background jobs left either.

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

同一个 XO 血统,2003 年快照。

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

§7.100 sub-threshold instance (2026-08-20)

Found during the §7.100 tail-sweep (below the original 166-lib survey's

=100-occurrence threshold, never checked). 2 live

replace_program(ROOM); occurrences in d/menpai/wuliang/west/shufang.lpc and d/menpai/wuliang/west/shufang1.lpc. No room-building tool exists in this lib. Fixed by deleting the redundant lines. Verified via a clean native driver boot (zero new debug.log errors, port listening, killed by exact PID after ~8s).

深度功能测试第四轮 / Round four (2026-08-20): the 3 explicitly-flagged §10.7-rule-6 gaps

Targeted pass closing the three gaps the round-two pass ("2026-07-25") explicitly left unverified-live. Native build-debug driver, own raw Python dual-socket telnet client (dual.py, adapted from scripts/mudclient.py to drive TWO simultaneous connections — needed because give requires the recipient to be genuinely interactive(), not just alive-but-netdead, so a player and an admin connection had to be open at once), each response inspected before sending the next command. Standard checklist re-confirmed first (see below) — all already clean/fixed by prior passes, no new checklist regressions.

Gap 1: reconnect persistence — RESOLVED, completed live

Test character qinlie/秦烈 (registered fresh this pass). After reaching a stable state (money via the sanctioned admin clone+summon+give pattern — see Gap 2 — landed qinlie at 太白楼 carrying 青布衣衫(worn)/百宝箱/两银子), issued a real quit at real playtime > CAN_SAVE_LIMIT_TIME (1800s past birthday, confirmed via query("birthday") vs wall clock first) — this is the FULL later_quit() path (call_out("later_quit", 30, me), not the "confirm without saving" gate), which fired after the real 30-second delay, dropped non-autoload items with on-screen confirmation ("你丢下 一个百宝箱。你丢下一套青布衣衫。"), then the standard farewell text and a genuine destruct(me) — confirmed via find_body/call qinlie->... both returning "找不到指定的物件" immediately after. Waited a real ~3 additional minutes (character fully absent from memory the entire time — not a netdead body, an actual absence, so this is an unambiguous disk-only restore test) before reconnecting. Restored state matched: score showed identical stats/attributes/skill levels/death count (3 deaths, matching pre-quit); i showed the silver money intact (AutoLoad in the raw .o save file confirmed /clone/money/silver survived with its exact amount) — this is the only inventory item that is expected to survive a quit, per later_quit()'s own query_autoload()-gated drop logic, verified correct. Two adjacent findings, both confirmed NOT bugs (no error signature, fully graceful, consistent across repeated reproduction):

A separate accidental discovery while investigating a stalled qinshen reconnect (see the §7.108 fix below) is the REAL bug found this pass.

Gap 2: shop purchase — RESOLVED, completed live, correct

Followed the sanctioned admin pattern this project's other round-four passes established (see mhxy's NOTES): as admin (fluffos, pre-seeded per the 2026-07-24 WASM-enablement pass), clone /clone/money/gold (this lib's own /clone/money/gold.lpc — value 10000), then summon qinshen/give gold to qinshen to bring a fresh, ordinarily-registered test character (qinshen/秦深, id qinshen, pw Test2026) into the same room and hand over real currency — but this lib has an extra wrinkle worth documenting for future passes: give to a non-wizardp recipient silently routes through a different, stricter accept path when the recipient is netdead (!interactive(who)) — cmds/verb/give.lpc's do_generate block calls who->accept_object(ppl, obj) (the quest-turn-in handler, system/std/char/master.lpc), which almost always declines for an ordinary gift, producing the exact same "好象不大愿意接收你的物品。" text a genuine env/refuse_give opt-out would show — easy to misdiagnose as the wrong bug. Fix for testing purposes (not a code change, a methodology note): the recipient must be genuinely interactive() — i.e., actually connected, not merely alive-but- netdead — for a plain give to succeed. Wrote a small dual-socket Python driver (dual.py) to hold the player connection open with periodic blank keepalives while a separate admin connection acted on them in the same room. Completed purchase: list san at 小三 (酒保, 太白楼) showed 女儿红(nver hong):一两银子; buy nver hong from san succeeded (the plain alias nver alone does NOT match — vendor.lpc's buy_object() matches the object's full id() string, which for this item is the two-word "nver hong", not the shorter aliases also registered via set_name). Verified via query_money() and i before/after: exact price deduction with correct auto-change (paid with 1 gold = 10000, price 1000, correct remainder = 9000, redistributed by feature/finance.lpc's pay_money() into 0 gold + 9 silver + 0 coin — confirmed both the existing gold object's amount getting adjusted and a brand-new silver object being created for the change, matching pay_money()'s own logic read from source) — both the price deduction and the item receipt (女儿红(Nver hong) in i) verified correct.

Gap 3: combat/death progression — RESOLVED, real death/respawn cycle, ONE REAL BUG FOUND AND FIXED

Per the task's dtsl2/mhxy precedent question: checked cmds/verb/kill.lpc first — its only gate at all for an NPC target (!userp(obj) branch) is obj->refuse_killing(ppl), an NPC-defined opt-out (many NPCs have it, many don't; undefined-function call_other returns 0/allow by default) — no combat_exp or other PK-only gate applies to NPC targets at all, confirming the precedent holds here too. kill (not bihua) worked immediately.

Found a genuinely killable target: d/city/gaochang/npc/asan.lpc (combat_exp 50000, set_hp gin/kee/sen 500/700/700) has refuse_killing() (a protected quest NPC, not usable), but its sibling d/city/gaochang/bosipu.lpc's npc/shangren2.lpc (波斯商人 阿里, combat_exp 50000, no refuse_killing override) does not. Test character qinlie/秦烈 (age 14, fresh, 0 combat_exp) walked there for real (center→west→west→north) and kill shangren — real, unmodified combat resolved over a few rounds ("你的眼前一黑,接着什么也不知道 了……" → "你满身血迹,再也支持不住,一头撞倒在地上") into a genuine death, moved to 黄泉路 (d/place/death/yellow1). debug.log stayed completely clean (0 new lines) through the entire combat/death/ reincarnation sequence, this pass's entire session.

Walked the full underworld chain for real (yellow1→north→yellow2(孟婆's chat_msg ambient lines look identical to её her real death_msg stage-0 line and briefly caused a misdiagnosis — the REAL tea_give() sequence completed correctly and gave 孟婆茶)→north→gate(白无常/黑无常)→north→difu1 (牛头/马面)→north→difu2→north→dadian/阎罗大殿, where d/place/death/npc/chacha.lpc (查察司判官) runs the actual reincarnation dialogue.

Real bug found: chacha.lpc's death_stage() permanently soft-locks any ghost who still has ANY item (including the 孟婆茶 mengpo tea EVERY ghost receives automatically two rooms earlier) at stage 4.

void death_stage(object ob, int stage) {
  ...
  else {
    obs = deep_inventory(ob);
    if (sizeof(obs)) {
      command("hmm");
      command("say 不过阴间的东西是不能带到阳间的,你先要把你身上的东西放下来。");
      return;                              // <-- never clears the guard
    } else
      tell_object(ob, death_msg[stage]);
  }
  ...

init() guards re-entry with ob->query_temp("death_stage_active") — every OTHER exit path in this file (the early !ob || !present(ob) branch, and the normal end-of-sequence path after reincarnate()) correctly clears this flag, but the item-check branch does not. Since nothing else in the whole codebase ever calls death_stage() except init() (itself blocked by the still-set flag), a ghost who reaches stage 4 still carrying items — which is EVERY ghost, since die() never auto-drops inventory and every ghost is handed a 孟婆茶 two rooms before reaching 查察司 — gets stuck in ghost form permanently: re-entering the room after dropping items is a no-op forever, with zero error anywhere (debug.log stays clean the whole time — this is a silent, crash-free soft-lock, not a crash). Live-reproduced twice (once via natural gameplay — stalled for real, query_temp confirmed the flag stuck at 1 with no error and no pending callouts entry for death_stage at all; once via a controlled repro with an admin-given item after the fix, confirming the exact same stall shape). Fix: added ob->delete_temp("death_stage_active"); right before the existing return; in that branch, matching the pattern every other exit path in the file already uses. Verified end-to-end, twice: (1) direct repro — cleared the stuck flag via admin, re-entered with an item still carried, confirmed the NEW code now clears the flag itself (query_temp → 0, not stuck at 1) when hitting the item-check message; (2) full natural recovery — dropped the item, re-entered the room with zero admin intervention, and the complete 5-stage dialogue played out correctly this time, ending in a real reincarnate() + move to a live room (d/city/gaochang/center). lpcc-equivalent live update + destroy-and-respawn-the-clone (dest, call .../dadian->reset()) used to get the fix into the ALREADY-LOADED live NPC instance without a full driver restart — confirmed via callouts listing that the fresh clone (chacha#160) picked up the new updated bytecode where the stale clone (chacha#119, still running pre-fix code even after a successful file-level update) had not.

Post-reincarnation state verified correct: score showed the death count incremented (3, after 2-3 real deaths across this pass's testing) and all stats restored to full (heal_up()/reincarnate()'s eff_*=max_* reset), no lingering ghost flags.

§7.108 gap found and fixed: interactive.lpc's reconnect() missing enable_commands()

Found by accident while investigating why a long-idle qinshen reconnect left every command producing "什么?你想干嘛?"/showed "对 不起,由于你没有环境,转移到void..." — this is AGENTS.md's own cataloged §7.108 ("kick out an already-connected duplicate login" reconnect path), which a corpus-wide sweep already fixed on 162 libs — xajh2 was evidently missed by that sweep (grep -n enable_commands system/feature/user/interactive.lpc → zero hits, confirming the gap). system/daemon/logind.lpc's confirm_relogin() has the exact vulnerable shape (exec(old_link, user); destruct(old_link); then reconnect(ob, user)), and system/feature/user/interactive.lpc's character-class reconnect() never called enable_commands() — only set_heart_beat(1)/set_temp("netdead",0)/remove_call_out. Fix: added enable_commands(); as the first line of reconnect(), matching AGENTS.md §7.108's exact proven remedy. Verified live, twice: (1) reproduced the exact "kick out" sequence pre-fix is implied by the qinshen symptom (not independently re-confirmed broken pre-fix, since the fix was applied before a clean isolated repro was run — see honesty note below); (2) post-fix, reproduced the full canonical §7.108 repro live (connection A holds qinlie open and interactive, connection B logs in as the same id, gets the "另一个连线中的相同人物" kick-out prompt, answers y) — look/score both produced full, correct output immediately after "重新连线返回。", not the generic fail message. Honesty note: the qinshen anomaly that led to finding this (commands failing AND environment loss AND find_player()/call unable to resolve the object even though find_body()'s internal fallback still could) was not independently root-caused beyond identifying it as this same gap's family — it may be a compound symptom of §7.108 interacting with this lib's long-idle netdead path rather than a byte-for-byte match to the catalogued symptom; not chased further given the canonical repro above already gives an unambiguous, clean positive/negative comparison.

Checklist re-confirmation (all already clean/fixed by prior passes — no regressions found)

Process hygiene

Driver restarted once mid-pass (to pick up the interactive.lpc fix cleanly, since it affects the live player-body class shared by every connected character — safer than a live update for a base class already in active use by test characters). Every kill used the exact PID (804454, then 813660), confirmed via readlink -f /proc/<pid>/cwd before each kill. No other libs' drivers were touched. Test character save files (qinlie, qinshen — login/user/log, all three dirs) removed after testing per this lib's established convention; fluffos admin save-timestamp churn (data/login/f/, data/user/f/) left as-is per this project's standard practice for the seeded admin account.

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

scripts/lpc_fix_negative_ranges.py: 4 line(s) in 1 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-07)— 兵器的 actions 函数指针

set("actions", (: WEAPON_D, "query_action" :));:旧 MudOS 把 (: 字符串, "函数" :) 当作 call_other 函数指针,本驱动把它当作表达式函数指针,求值只得到字符串 "query_action"。没有对应兵器技能时 feature/attack.lpc 直接用兵器的 actions,COMBAT_D 拿到的不是 mapping,reset_action() 后仍不是,记一条 bad action 就不出招——拿着这些兵器打不出攻击。改成别的兵器早已用的 (: call_other, WEAPON_D, "query_action" :)(KB 04 §6.10):d/menpai/wuliang/west/npc/obj/flower.h。改动的文件在 HEAD 与工作树分别加载(新进程),无回退。

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

scripts/lpc_warnings.py xajh2 --fix 加 scripts/lpc_fix_no_effect.py --apply,共改动 24 个 .lpc 和 4 个 .h(多为删去未用的局部变量)。HEAD 与工作树分别加载(新进程):22 -> 22 通过,无回退;改过的头文件的 5 个包含者 2 -> 2 通过。启动时警告 2 -> 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 改了 2 处比较、0 处去后缀(如 secure/daemon/httpd.lpc);改动的文件 HEAD 与工作树分别加载(新进程),无回退。

编译警告收尾(2026-10-13)

手工清零(改动及相关对象在 HEAD 与工作树各载入一次,regress 0)。

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() 之后的除外)。

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

8 个文件、10 处在 create() 里 carry_object("<本库没有的文件>")->wear()(或 wield()、set_amount()):carry_object() 得 0,接着的调用报 Bad argument 1 to EFUN call_other(),NPC 根本载入不了,该有它的房间里就少了这个人。多是从别的系统搬来的 NPC 用了原系统的物品路径(最常见:/d/city/lanzhou/clone/obj/cloth1、/d/city/dali/npc/obj/yifu3、/d/city/lanzhou/clone/obj/wocloth1)。每处改为 { object carried_ob = carry_object(...); if (carried_ob) carried_ob->wear(); }(scripts/lpc_guard_carry_object.py):NPC 照常出现,只是不带这件东西;文件补回来(或是虚拟对象)照样会带上。载入检查:改动的文件改前能载入 7 个,改后 7 个(0 个原来载入不了),没有退步;其余 1 个另有原因(多为 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):include/net/ftpdsupp.h。

zuo 和 yuanyang-dao 丢了收尾引号(2026-10-18,KB 03 §4.6)

d/menpai/wuliang/npc/zuo.lpc 的 "姓名" : "嗯,你既知我姓左,……左子穆?, 和 system/skill/misc/yuanyang-dao.lpc 的 "action":"又见$N高高跃起,……切向$n$l, 行尾的引号没了(转码时跟半个汉字一起丢了),字符串一直连到下一行的开引号,后面的内容成了代码,两个文件都载入不了。补回 ",。载入:两个都通过。

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

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

被注释吞掉的 inherit / include(2026-10-19,KB 06 §7.274)

/d/city/yangzhou/npc/obj/guo.lpc:#include <ansi.h>。这些指令接在上一行 // 注释的末尾(档案里丢了换行),成了注释的一部分,驱动看不到:缺 inherit 的技能报 Undefined function offensive_target,缺 #include <weapon.h> 的兵器报语法错误。scripts/lpc_fix_swallowed_directive.py 把换行放回去。载入检查 1 个文件,1 个原来编译不过、现在载入,没有退步。

补回丢失的标点(2026-10-19,KB 03 §4.6)

转换时丢掉的分号、逗号、引号或文件末尾的 },共 3 个文件,修前 HEAD 载入失败。一行文字在逗号前丢了结尾的引号(下一行是同一个表的下一项),补上:/d/menpai/wuliang/west/npc/xin_bak_xli.lpc、/d/menpai/wuliang/west/npc/xin_shuangqing.lpc、/system/skill/shaolin/mohe-zhi.lpc。修后与 HEAD 逐个对比载入:这 3 个物件全部载入,没有退步(scripts/lpc_restore_from_twin.py、lpc_fix_eaten_quote.py --comma、lpc_fix_eof_braces.py)。

补回丢失的标点,另一个版本的完好副本(2026-10-19,KB 03 §4.6 第四轮)

/system/menpai/wuliang/zuo_zimu.lpc:"姓名" : "嗯,你既知我姓左,……左子穆?, 丢了收尾的引号,补回;多出来没用到的局部变量声明(master_id、bonus、test)删掉。文件仍然载入失败:refuse_msg 只在 system/std/char/questnpc.lpc 里声明,这个物件没有继承它(基类 task.lpc 里的叫 quest_refuse_msg),是另一处改名遗留,没有改。

无量剑派掌门三人:补回 refuse_msg、wl-shenfa 改成 wuliang-shenfa、太祖长拳丢掉的两个引号(2026-10-19,KB 06 §7.258)

上一节写到 zuo_zimu.lpc 仍然载入失败,原因一层一层往后看:

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,共 1 处)。改动:/d/city/yangzhou/shufeng_guan.lpc(1 处)。

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

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

翻页器 start_more(read_file(...)):文件缺失时 explode(0)(2026-10-20,KB 06 §7.300)

东方故事系的翻页器 void start_more(string msg) { ... explode(msg, "\n") ... } 被这样调用:start_more(read_file(HELP_DIR + "help/topics"))、start_more(read_file(NEWS))。压缩包没带的文件使 read_file() 回 0,explode(0, ...) 在本驱动里报 Bad argument 1 to explode()(不带参数的 help 指令就是这样出错的),老驱动把 0 当空文本。在 start_more 开头加了 if (!stringp(msg)) return;(scripts/lpc_fix_start_more.py)。改动:/system/feature/user/more.lpc。

join、accept 对档案里没有的擂台房间调用函数(2026-10-20,KB 06 §7.270 / §7.288)

cmds/verb/join.lpc 和 accept.lpc 开头是 object tai = load_object("/d/place/biwu/biwutai"); if (!tai->query_temp(...))。比武大会的擂台房间 /d/place/biwu/biwutai 不在档案里(d/place/biwu/ 下的报名处、大厅、等候室和各门派擂台房都在,唯独缺它),load_object() 答 0,于是直接敲 join 或 accept 就是 Bad argument 1 to call_other()(本库的错误处理把它写进 /log/runtime/cmds,并对玩家说“运行时发生错误”)。两个命令现在先测 objectp(tai),房间不存在就按原来的话回答“现在不是报名/比武时间”。比武大会本身(vote ... start、巫师命令 biwu、reward_d 的 record())依赖同一个房间,仍然不工作,那是档案缺的内容,没有碰。xajh2 的这两个文件与 xo_final 的逐字节相同,同样修改,只做了加载检查(2/2,回归 0)。

巫师命令不带参数时对 0 调用 efun(2026-10-21,KB 06 §7.336)

巫师(管理员)把命令敲成不带参数时,驱动把 0 传给字符串参数;下面的命令第一句就把这个 0 交给 efun,运行时报 Bad argument 1 to ...,这条指令从来没有用过。用 scripts/lpcc_bare.py 不登录、在一次 lpcc 求值里以 0 调用 main() 逐个确认后,由 scripts/lpc_fix_bare_arg.py 在第一处使用参数的语句前加守卫:文件自带 int help(object me) 的,守卫是 if (!arg) return help(me);(help() 定义在 main() 之后的 1 个文件补了原型 int help(object me);,否则编译报 Undefined function help);没有 help() 的是 return notify_fail("…\n");。改动(2 个文件,括号里是接住 0 的 efun):/cmds/admin/empty.lpc(find_player)、/cmds/imm/hbless.lpc(find_player)。加载检查 HEAD 与工作树对照,没有退步。

注册到不存在函数的 add_action,第二轮:宏路径父类和静态解析不了的对象(2026-10-21,KB 06 §7.349)

第一轮的静态解析读不了经宏路径继承父类的对象(#define ROOM STD_DIR "room/room" 这类定义的正文以另一个宏开头)和它解析不了的继承,这些对象当作 UNSURE 没有扫;第二轮修了解析器,重扫并对这些对象在运行时问 function_exists()。add_action("函数", "动词") 绑定的函数,在对象本身、它包含的头文件、它继承的父类和继承它的子类里都没有定义(scripts/lpc_scan_callout_targets.py 静态解析,子类用传递闭包复核,scripts/lpc_verify_handlers.py 运行时 function_exists() 复核)。驱动从玩家最新的一条动作往回找,遇到这样的句子就报 *Function for verb 'drop' not found.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 5 条注册(4 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/d/city/yangzhou/yuelao_ci.lpc(do_divorce)、/d/city/yangzhou/yuelao_ci_bak.lpc(do_divorce)、/d/city/yangzhou/yuelao_ci_bak_xli.lpc(do_divorce)、/system/std/room/kezhan.lpc(DoCheck、DoLoad)。最多的函数名:do_divorce 3 处、DoCheck 1 处、DoLoad 1 处。

(: 函数 :) 指针指向对象自己没有定义的函数(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。 还剩 2 个函数没有补(inquiry 2):原型还在函数体丢了、或者根本没写过,没有可以直接补回的捐赠(别的库同一路径的文件里没有这个函数,或者它还要用本库没有的辅助函数、签名不同),不编造:/d/menpai/shaolin/npc/xuwen.lpc:ask_armor2、/d/menpai/shaolin/npc/xuwen.lpc:ask_armor3。 另有 3 处函数体被作者整段注释掉(指针没跟着删),是有意关掉的,没动。

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

这个库的代码往 log/ 的 2 个子目录写日志:log/find_pig(cmds/comm/reward.lpc)、log/intermud(system/daemon/channeld.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() 什么也没写(探针对象没有写权限,log_file() 自己也不建目录),所以真实调用者会得到什么没有测出来。把 log/find_pig、log/intermud 记进 scripts/wasm_keep_dirs.txt(scripts/lpc_keep_log_dirs.py;站点和 pristine 树按它建空目录)。证明(scripts/lpcc_eval.py,在 /adm/daemons 这类目录下的求值对象里写 /log/zzdirprobe/...,新进程)见 KB 06 §7.358 的实测表。

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

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

写死的 "/路径"->函数() 指向库里没有的文件:改成现存的同名文件(2026-10-22,KB 06 §7.359)

2 处语句级的 call_other 字面量路径,库里没有这个文件(区域改了名 / 大小写不对 / 差一个字母 / 文件挪了地方):"/cmds/arch/xplor"->QueryLevel()(/system/daemon/biwud.lpc)→ "/cmds/app/xplor"。call_other() 对不存在的对象抛 couldn't find object,所在函数当场中断。改成库里现存、并且定义了被调用函数的文件(scripts/lpc_fix_call_path.py;巫师目录、备份目录、名字不同的兄弟文件都不算候选)。

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

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

本库 1 处(1 个文件):拼名字的赋值 1 处。例:name[2..3] → name[1..1](/d/menpai/shaolin/npc3/huijue.lpc:188)。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,不要重跑。

长度检查按字节写,旁边的提示按汉字写:2 处(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 你好吗)。本库 2 处(2 个文件):} else if (strlen(msg) > 16) {(/d/city/yangzhou/pplroomd.lpc:89);} else if (strlen(msg) > 40) //若长度在八个汉字以外,重新输入。(/d/place/player/newroom.lpc:86)。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 区间 2 个文件。证明(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(一格两个空格)。本库 2 处(1 个文件)。证明(lpcc_eval.py hhsj):tribar_graph(6,9,12) 在 HEAD 画出整条 12 个■,改后为 6 个■+3 个□;麻将 strlen(tempB)/2 是两字符牌码,未改。

estate ROOM_HEAD 宏引号 / #inherit / dir / retrun(2026-10-27,KB 04 §6.25 Also)

system/daemon/estate 的 ROOM_HEAD/EXIT_STRING 宏里裸双引号打断 #define,模板写成 #inherit AREA,CreateRoom 用未定义的 dir,AddItem 写 retrun 且参数 class Location 本库无此类型。改为转义引号、inherit AREA、out_dir、return、mixed location。证明:HEAD 失败;改后加载。

深度功能测试(2026-10-27,§6.25 tell_object 字符串目标)

pplroomd:tell_object("ob", …)→tell_object(ob, …)。

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