Xia Ke Xing — 2017 Complete Edition (Ode to Gallantry)

✅ 可玩

侠客行 (MUD侠客行2017完整版)

xiakexing2017

🔑 fluffos / Mud@2026 更新 6e86eb7 2026-10-27 源码 下载 ZIP

▶ 开始游玩 · Play Now

取材自金庸小说《侠客行》,压缩包标注为"2017完整版";与本项目中同名"侠客行"游戏 xiakexing100 代码库彼此独立(内容匹配度仅约 2%-4%),但经过逐档比对,它其实与本项目另一款"侠客行三"xiakexing3、以及以"金庸群侠传"为名的 jqxz2008 系列是关系密切的近亲——共有路径下约 80%-84% 的文件逐字节相同,是同一套 adm/single/master.lpc 服务端核心与同一个基础游戏世界(茶馆/客栈起步、丐帮枯树洞收徒、明教弟子按"缘"字辈份编号取名等)在不同站点换皮后产生的多个内容快照,而非各自独立开发的游戏。

English

Based on Jin Yong's novel Ode to Gallantry (Xia Ke Xing) in its own login banner, this archive is labeled the "2017 Complete Edition." Despite the shared name, its codebase is genuinely independent of this collection's xiakexing100 (only ~2-4% of files match by content). It is, however, a much closer relative of xiakexing3 and of this collection's jqxz2008/jqxz2008dlx/jqxz2008std/jqxz2015 "Legend of Jin Yong's Heroes" (Jin Yong Qunxiazhuan) family than either side's own description previously acknowledged: a direct file comparison found roughly 80-84% of the files it shares a path with are byte-identical to both xiakexing3 and to jqxz2008 — one server program and one base game world (adm/single/master.lpc core, tea-house/inn start, Beggars'-Sect-via-hollow-tree recruitment, Mingjiao's numeric-curse-named NPCs) produced across several separately-branded content builds, not independent games. New characters start at an inn, can apprentice into named sect teachers (e.g. the Beggars' Sect's 余洪兴/Yu Hongxing), and the game clock displays "Xia Ke Xing time," real Beijing time, and the lunar calendar side by side.

README

内容亮点

在线试玩

https://mudlibs.fluffos.info/xiakexing2017/

管理员账号 / Admin account

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

本地运行

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

游戏端口:40017。

NOTES · 移植与修复记录

MUD侠客行2017完整版.zip → xiakexing2017

Status: DONE — boots clean, playable over telnet

Zero fixes needed — clean boot and full login flow on the first attempt.

What was fixed

Nothing needed for boot. Routine conversion: encoding (3330 converted, 521 already UTF-8/ASCII, 59 skipped binary, 15 lossy), .c→.lpc (3266 files, 146 refs auto-fixed), 8 local angle-bracket includes converted to quotes automatically, static→nosave (42 files).

Known remaining issues

How to run

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

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

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

Re-verification pass (driver rebuild + LPC formatter + WASM build)

WASM-enablement pass (loopback-allow / admin seeding)

This lib's adm/daemons/band.lpc/logind.lpc are byte-identical (per §2.1 lineage check) to the xiakexing3/jqxz2008 engine files, whose WASM pass was already done and verified with the correct fail-closed convention — ported that fix here and re-verified independently rather than assuming it.

Admin account: fluffos / Mud@2026 / 浮浮, registered through the real flow (id → y → 浮浮 → password ×2 → talent accept y → email → m). Granted (admin) by appending fluffos (admin) to adm/etc/wizlist (shipped with rwz (admin) already present, no trailing newline — added one; securityd.lpc reads this file at create(), so a driver restart was needed to pick up the new line). Verified after restart: 目前权限:(admin), title 【天神】, update /adm/daemons/band → 重新编译 /adm/daemons/band.lpc:成功!.

Save files for the orchestrator to force-add (untracked, not gitignored):

Two pre-existing caught (non-fatal) restore_object(): Illegal file format errors were observed in debug.log during this pass — /adm/daemons/chinesed's save data at boot preload, and /clone/board/kedian_b's board save data at enter_world() (matches the raw-path /clone/board/kedian_b display already visible in the game, instead of a proper board name — its corrupted save data is why). Both are wrapped in catch() by master.lpc's preload()/ logind.lpc's own catch respectively, non-fatal, and confirmed via git log/git status to be genuinely corrupted data already committed as part of the original archive conversion (archive #20, "booted clean with zero fixes") — not introduced by this session. Left as-is per the project's established emoted.o/similar precedent; not a new regression.

Retest: fresh boot, fresh registration (id qretest, real Chinese name 秦风十一) through look/score/quit — landed in 客店, correct output, clean quit. fluffos/Mud@2026 admin login verified via score (confirms (admin)/天神) and update. debug.log had exactly the two pre-existing corrupted-save errors above and zero NEW runtime errors. Test character qretest removed afterward; fluffos kept.

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

First real *playthrough* pass on this lib (all prior passes verified only registration + look/score/quit, or watched boot output). Read doc/help/newbie and doc/help/intro in full first — this is the "东方故事2" (ES II) -derived 金庸-themed game: newbie names the fight (safe, to-half-health sparring) vs kill (real) distinction up front, the bai/xue teacher-apprentice loop, the 扬州 (Yangzhou) starting-city layout with turn-by-turn routes to every sect, and dozens of shortcuts. Played one continuous native-driver (build-debug) session as an ordinary new player: registration with a real Chinese name, exploring Yangzhou (客店→北大街→中央广场→东大街→东门, then the deterministic ml1–ml7 "青竹林" maze into d/city/pomiao and the 丐帮 hideout gbandao), the organic bai/apprentice sect-join path against four different 丐帮 teacher NPCs (all correctly rejected per their own, met-or-not stat/alignment gates — not a bug, see below), the fight safe-sparring command against a 流氓 (real multi-round combat output, no crash), a list/buy shop-purchase attempt (correctly rejected for insufficient funds), a real clean quit→relogin round trip (state preserved), a real prompt net-dead reconnect (uncleanly closing the socket, reconnecting ~25s later — resumed in the exact same room, confirming this lib's net_dead() never void-parks the player at all, see "§7.20 checked" below), and — critically — a real full- duration (600s = NET_DEAD_TIMEOUT) unclean-disconnect wait, which crashed the native driver process outright. Found and fixed four separate bugs: one already-cataloged class (§7.12, promoted to CRITICAL severity by this pass — the driver crash above), one recurrence of an existing class applied at a more general choke point (§7.25- shaped), and one genuinely new class (drafted below) that hit in three independent files.

Primary test character (kept, state left as evidence): id qinfengxia, Chinese name 沈秋白 (male), password Xia@2026. Rolled stats 膂力19/悟性26/根骨21/身法14 (all four teacher-rejection messages below are a direct, verifiable consequence of this exact roll, not a bug). State: explored Yangzhou + the 丐帮 hideout, sparred a 流氓 via fight (both survived, health text on both sides — matches the "点到 为止" safe-sparring description in help combat/help intro), no sect/skill acquired live (see honesty note below), survived a clean quit→relogin, a prompt net-dead reconnect, and — after the fix below — a real full-duration net-dead force-quit + fresh-login verification. Currently sitting at 客店 (start room) after a final clean quit. Saves: work/data/user/q/qinfengxia.o, work/data/login/q/qinfengxia.o.

Secondary throwaway character: id goumaice, Chinese name 顾买测 (female), password Gm@2026. Used to exercise the shop list/buy flow (correctly rejected, no money) and — deliberately left net-dead at the same real moment as qinfengxia — as the second of the two simultaneously-net-dead characters needed to reproduce the §7.12 driver crash (see Bug 1). Saves: work/data/user/g/goumaice.o, work/data/login/g/goumaice.o.

§7.20 (void-parking) explicitly checked and does NOT apply to this lib: read clone/user/user.lpc's net_dead()/reconnect() in full — this lineage never moves a disconnected player to a VOID_OB/holding room at all; the player object simply stays in its current room with a netdead temp flag while call_out("user_dump", NET_DEAD_TIMEOUT, DUMP_NET_DEAD) waits, and reconnect() (confirmed actually invoked, adm/daemons/logind.lpc:481) just clears the flag in place. No location to lose, no restore path needed. Confirmed live with both a prompt (~25s) and a full 600s-plus net-dead reconnect: character stayed in the same room both times.

Bug 1 (CRITICAL — already-cataloged class, AGENTS.md §7.12, severity re-confirmed): the shared tell_room() simul_efun wrapper forwards an unset exclude varargs as a raw int 0, crashing message() — reached from the NET_DEAD_TIMEOUT force-quit path, and reproduced as an actual native driver process crash

File:line: adm/simul_efun/message.lpc:36-38.

This is the identical bug AGENTS.md §7.12 already documents (first found on yueyingqiyuan, severity-escalated on dtsl's own §10.7 pass) — citing the existing class, not redrafting it. Filed here because this lib turns out to carry a byte-identical copy of the vulnerable adm/simul_efun/message.lpc (not currently listed as a sibling of dtsl/yueyingqiyuan in AGENTS.md §11's lineage map — worth a lineage note) and because this pass independently reproduced §7.12's full "severity escalation" scenario live, including the driver crash, on a *third* lib:

`` md: debugmalloc: attempted to free non-malloc'd pointer ... #3 MDfree ... #4 debugfree ... #5 dealloc_object ... #6 int_free_svalue #7 assign_svalue ... #8 assign_value_to_lvalue ... #9 eval_instruction (F_ASSIGN) ... #12 f__call_other ... #17 call_function_interactive (processing the reconnect command) ... Aborted (Signal sent by tkill()) ` Confirmed via ps/ss that the driver was genuinely gone (not just the connection dropping) and via the stdout backtrace, not inferred. The exact C-level mechanism (a destructed-object reference whose refcount reached 0 without a proper free_object`) wasn't rigorously proven with ASan/valgrind (out of scope), matching AGENTS.md §7.12's own caveat, but the correlation is strong: the crash did not recur after the fix, reproducing an equivalent two-character scenario.

``lpc // BEFORE: varargs void tell_room(mixed ob, string str, object *exclude) { if (ob) message("tell_room", str, ob, exclude); } // AFTER: varargs void tell_room(mixed ob, string str, object *exclude) { if (ob) message("tell_room", str, ob, exclude || ({})); } ``

Lineage note for AGENTS.md §11: xiakexing2017's adm/simul_efun/message.lpc carries this exact vulnerable tell_room() shape, matching dtsl's. Currently listed as "Standalone/distinct" in §11 — worth flagging as sharing at least this one shared-infrastructure file with the broader ES II family (§11's "ES II / 东方故事 mega-family" already carries the §7.12 warning), even though this project's own newbie/intro docs describe it as "东方故事2" (ES II) -derived already, so this is consistent, not a surprise.

Bug 2 (new bug class, draft below): mapping-typed properties/accessors default to a raw int 0 when never set, and several call sites index into them without checking — "value being indexed is zero" / "bad argument 1 to keys()"

Two independent instances, one in shared engine code (reachable from nearly every room in the game) and one in specific zone content — same underlying shape, draft text below covers both.

Instance A — feature/skill.lpc's query_skills()/query_learned()/ query_skill_map()/query_skill_prepare() (lines 13-14, 103-109): each is declared mapping query_X() { return X; } where the module variable X (skills/learned/skill_map/skill_prepare) is declared mapping X; but never initialized — LPC's default value for an unset mapping global is int 0, not ([]). Every setter in the same file (set_skill(), map_skill(), etc.) correctly guards with if (!mapp(skills)) skills = (...), but the four query accessors return the raw variable unconditionally. Reached live: d/npc/xiaofeng.lpc's check_skills() (fired via a 2-second call_out from init(), i.e. on ordinary room entry) does skl = ob->query_skills(); sname = sort_array(keys(skl), ...) — for our fresh test character (0 skills learned, completely ordinary, expected state for a brand-new player), query_skills() returned raw 0, and keys(0) crashed:

执行时段错误:*Bad argument 1 to keys()
Expected: mapping Got: 0.
程式:/d/npc/xiaofeng.lpc 第 99 行

84 call sites across 42 files call ->query_skills()/->query_learned() — most already defensively re-guard with their own if (!mapp(...)) before use (e.g. cmds/std/check.lpc, cmds/skill/skills.lpc, which is exactly why this didn't surface everywhere), but at least this one did not.

Instance B — a mapping-typed per-object query() property indexed directly, same shape, different mechanism: query("party") (an object's faction-membership record, unset for anyone not in that faction) and query("family") (sect-membership record, unset for anyone sect-less) both legitimately return 0 for the common case of "hasn't joined". Three files indexed one of these directly with no guard: d/mingjiao/npc/mingjiao.h:6 and its byte-identical duplicate kungfu/class/mingjiao/mingjiao.h:6 (ob->query("party")["party_name"] in a shared greeting() used by ~20+ 明教 NPC files, fired via a 1- second post-init() call_out — i.e. crashes for any non-明教 player standing near ANY of them, confirmed live for d/mingjiao/npc/ changyuchun.lpc's 常遇春, who sits in the starting city's central square that every fresh login passes through), and d/xiaoyao/obj/ bojuan.lpc:43 (me->query("family")["family_name"], a readable skill scroll — crashes for any non-逍遥派 player who tries read/study/du on it, which is the obvious, intended first thing to try). All three crash with the same signature:

执行时段错误:*Value being indexed is zero.

This shape is NOT universal in this lib — most other query("family")[/ query("party")[ call sites (checked: kungfu/class/mingjiao/ zhangwuji.lpc, d/xiaoyao/xiaodao1.lpc, shishi4.lpc, npc/qingyun.lpc, npc/xuemuh.lpc, fangjian2.lpc) already correctly guard with `!ob-

query("family") ||/ob->query("party") &&` — confirming this is an

easy-to-miss oversight recurring independently, not evidence the whole convention is broken, and that the correct local idiom was already established and just not applied consistently.

Detection: grep ->query_skills(\|->query_learned(\|->query_skill_map( \|->query_skill_prepare( (or a lib's own equivalent mapping-returning accessor) for definitions that return <bare variable>; without a mapp() guard, matching the setter functions' own established discipline in the same file. Separately grep ->query("<mapping-shaped- property>")\[ (family/party/couple/skill-map-shaped properties) for direct indexing with no adjacent mapp()/truthiness check — cross-check against sibling call sites in the SAME file/directory first, since (as here) the correct guarded idiom is very often already established nearby and the unguarded instance is the outlier, not the norm.

Fix: at the accessor/property level, not every call site — same "shared choke point" principle as §7.25/§7.26. For an engine accessor like Instance A: mapping query_skills() { return mapp(skills) ? skills : ([]); } (applied to all four accessors in feature/skill.lpc). For a one-off unguarded index like Instance B: capture the property into a local first and guard it (party = ob->query("party"); if (mapp(party) && party["party_name"] == ...)), matching the idiom already used correctly elsewhere in the same codebase.

Verified: restarted the driver after each fix; re-walked qinfengxia past 萧峰/xiaofeng (北大街) and 常遇春/changyuchun (中央 广场) — both previously-crashing check_skills()/greeting() calls fired cleanly with zero debug.log errors afterward, confirmed across two independent fresh-boot sessions. bojuan.lpc's fix was verified by code-inspection parity with the other five already-correct sibling call sites in the same directory (not re-walked live to the 逍遥派 zone specifically — a multi-minute maze/gate trek for a one-line, pattern- identical fix already proven safe by the two live-verified sibling instances; noted honestly rather than silently claimed as live-tested).

Bug 3 (recurrence of AGENTS.md §7.25, fixed at a more general choke point): cmds/std/go.lpc's destination-room force-load silently swallows the player's move command on that room's first-ever visit when the room's own tail-of-create() companion-object load throws

File:line: cmds/std/go.lpc:70-73 (plus two smaller sibling instances at d/wizard/guest_room.lpc:24 and d/wizard/wizard_room.lpc: 21, same shape, fixed the same way).

Matches AGENTS.md §7.25's "closely related shape" almost exactly ("rooms that force-load a companion object ... via call_other('<path>', '???') in their own create()") — citing the existing class. The difference from a textbook §7.25 instance: the vulnerable call_other(dest, "???") isn't inside each room's own create(), it's in the single SHARED movement command (go.lpc) that force-compiles *any* destination room on its first-ever visit — so this fix was applied at that one shared choke point instead of repeating catch() across this lib's 17 separate rooms that end their own create() with an identical "/clone/board/xxx_b"->foo(); companion-board force-load (confirmed all 17 have the load as the LAST statement of create(), i.e. after exits/objects/setup() are already set — spot-checked 5 of the 17 directly, matching the one instance actually reproduced live).

``lpc // BEFORE: if (!(obj = find_object(dest))) call_other(dest, "???"); // AFTER: if (!(obj = find_object(dest))) catch(call_other(dest, "???")); ` Safe specifically because every instance checked (17/17 by grep, 5/17 spot-checked directly) puts the risky companion-object load as the LAST statement of the destination room's create() — so even when the catch() swallows a throw, the room object is already fully populated (exits/objects/setup() all ran) by the time go.lpc's retry find_object(dest) check runs immediately after, and the move proceeds normally into a correctly-initialized room. Also applied the same catch() to the two structurally-identical but currently- unexercised direct call_other(".../board", "???") force-loads in d/wizard/guest_room.lpc and d/wizard/wizard_room.lpc (their own create(), not reached via go.lpc` — the same §7.25 shape, proactive fix by code-shape match, not yet live-triggered since their board data isn't known to be corrupted).

Honesty notes — not verified live, explicitly

Files modified this pass

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

深度功能测试第三轮 / Deep functional test round three (2026-08-14)

Independently re-verified every §10.7 checklist item against current code rather than trusting the round-two writeup above; found and fixed one new bug (update.lpc), plus a 100%-reproducible recurrence of the already-cataloged AGENTS.md §7.90 eval-cost class.

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

File:line: cmds/wiz/update.lpc:20.

main()'s first check calls present(file, environment(me)) unconditionally. When environment(me) is 0 — reachable any time update is typed by an object that isn't currently placed in a room — this throws *Bad argument 2 to present() Expected: object Got: 0., aborting the whole command with no useful message.

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

Fix: cold-start eval-cost exhaustion during make_body() — recurrence of AGENTS.md §7.90 ("xyj2000f" variant), 100% reproducible, not intermittent

Initially misdiagnosed this as a debug-build-speed test artifact to note and leave alone. Checking AGENTS.md before drafting a new entry caught the mistake: §7.90 already documents this exact class, including a variant ("harsher variant on xyj2000f") that matches this lib symptom-for-symptom — eval-cost abort inside make_body() itself, 100% reproducible on every attempt (not the more common "only some never-visited rooms" flavor), no player-facing error, and §7.90's own prescribed and verified fix is to raise the committed maximum evaluation cost, not leave it alone. Applying that established fix here instead of inventing a new rationalization.

It reproduced on every single fresh-boot first login attempt, every time, across multiple independent boots, and did not cleanly self-heal the way this session's other libs' cold-start eval-cost hits did: the character that "succeeds" after the aborted make_body() lands in a session where every command (look, who, update) returns the generic "什么?" fallback — command dispatch never finished wiring up. Reconnecting doesn't help; the very next login attempt hits the identical abort, every time.

Re-verified still holding (code-inspection, not full live re-repro — see rationale)

All three round-two fixes remain present and unmodified in the current code (adm/simul_efun/message.lpc's exclude || ({}), feature/skill.lpc's four mapp()-guarded accessors plus the mingjiao.h/bojuan.lpc guards, and cmds/std/go.lpc's catch(call_other(dest, "???")) plus its two guest_room.lpc/wizard_room.lpc siblings) — confirmed by direct grep against each fix's exact diff, not merely assumed. Bug 2 Instance A was additionally re-verified live: walked to 北大街 where 萧峰/xiaofeng sits, waited past his check_skills() call_out, debug.log showed zero errors afterward (previously crashed with "Bad argument 1 to keys()" pre-fix). Bug 1's full NET_DEAD_TIMEOUT reproduction and Bug 3's maze route were not re-walked live this pass — both require multi-minute real-time or multi-step scripted routes already thoroughly live-verified twice in the round-two writeup above, and this pass's time budget went to the new update.lpc bug investigation instead; noted honestly rather than silently re-claimed as freshly live-tested.

Verification method

Two rapid consecutive reconnects as fluffos (both clean, both showing "目前权限:(admin)"), update /adm/daemons/logind used as the real privileged-action admin-rights check (not the login banner), driver killed by exact PID after testing, incidental fluffos.o save-timestamp churn reverted before commit.

Files modified this pass

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

#define ROOM "/inherit/room/room":删除 818 处多余的、独立成行的 replace_program(ROOM);(保留 inherit ROOM;),与 xkx2017/ jqxz2008 系列同一血统同一形状(本档案不是逐字节相同的手足档案, 独立验证)。clone/misc/roommaker.lpc 同样有两套模板——"造一间空 房间"的 heredoc 本来干净,"克隆我所在的房间"命令的字符串拼接模板 把同一枚多余的 replace_program(ROOM); 烤进了每一个新克隆的房间, 已同步修正。已用 build-debug 驱动干净启动验证(0 个新增编译错 误,端口正常监听);未做完整 §10.7 深度游玩测试。

``§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/newgargoyle.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.

深度功能测试第四轮 / Deep functional test round four (2026-08-20)

Resolved this lib's two remaining round-two/round-three "honesty notes — not verified live" gaps (sect apprenticeship completion and death/respawn) explicitly. Found and fixed one new, genuinely crash-causing bug in the process; the standard checklist re-confirm otherwise came back 100% clean (all prior fixes still present, matching the current code).

Item 1 — sect membership / xue skill learning: COMPLETED LIVE. New bug found and fixed: adm/daemons/chinesed.lpc's bare restore() (no local catch()) let a pre-existing corrupted save silently abort create() before its own mapping dict variable could ever be (re-)guarded, crashing every real xue/skills command

File:line: adm/daemons/chinesed.lpc:25-28 (pre-fix).

Re-examined the four 丐帮 teachers previously confirmed reachable without prior sect membership (鲁有脚/彭有敬 at d/city/pomiao, 简长老 at d/city/gbandao, 苏国盛 — actually 洪七公's man hong — at d/city/gbxiaowu/d/city/mishi, though the latter two turn out to be gated behind d/city/gbandao's own valid_leave() for non-members, so not actually reachable pre-membership either) and confirmed none of the pomiao/gbandao-reachable teachers can currently accept qinfengxia's actual roll (int 26): kungfu/class/gaibang/lu.lpc rejects if int>=25 (inverted from what a skim of the file names suggests — Lu wants *low*-int students), peng.lpc and jian.lpc are unconditional non-recruiters. This matches the round-two writeup's own conclusion, not a contradiction of it.

Read the full kungfu/class/gaibang/*.lpc roster instead of stopping at the four already-tried teachers: yu-hx.lpc (余洪兴, 丐帮八袋弟子) accepts if int>=25 — the *actual* NPC the original task brief's "int>=25 threshold" was describing, not lu.lpc. qinfengxia's existing int 26 already satisfies it — no stat grant was needed at all, only physical access. yu-hx.lpc sits in d/gaibang/underxx, reachable in real play only via d/city/pomiao's dong (树洞) passage, itself gated by (fam=me->query("family")) && fam["family_name"]=="丐帮" — i.e. the whole underground-tunnel teacher roster (li-sh, liang, yu-hx, zuo-quan, ma-jw, he-bj) is circularly locked behind already having a sect, so reaching it the "long way" isn't actually possible pre-membership either. Used the sanctioned admin-shortcut path instead: admin goto /d/gaibang/underxx (teleport the admin only, bypassing the tedious/circular travel gate, not any stat/security check) then admin summon qinfengxia (pulls the real character object to the admin's room via the ordinary move() mechanism — the same summon any admin could use in a real support situation) to physically place qinfengxia in the room without touching any stat. bai yu/apprentice yu then ran yu-hx.lpc's real, unmodified attempt_apprentice() check against the character's real, roll-derived int, exactly like the four previously-tested teachers.

`` 执行时段错误:*Value being indexed is zero. 程式:/adm/daemons/chinesed.lpc 第 94 行 呼叫来自:/feature/command.lpc 的 command_hook() 第 55 行,物件: /clone/user/user#12 ("沈秋白") 呼叫来自:/cmds/skill/xue.lpc 的 main() 第 71 行,物件: /cmds/skill/xue 呼叫来自:/adm/obj/simul_efun.lpc 的 to_chinese() 第 6 行,物件: /adm/obj/simul_efun 呼叫来自:/adm/daemons/chinesed.lpc 的 chinese() 第 94 行,物件: /adm/daemons/chinesed ``

``lpc // BEFORE: void create() { seteuid(getuid()); restore(); } // AFTER: void create() { seteuid(getuid()); catch(restore()); if (!mapp(dict)) dict = ([]); } ``

Item 2 — death/respawn: COMPLETED LIVE, fully clean, no new bug found

Real kill liu (matched 流氓头/Liumang-tou, the stronger of the two 流氓 NPCs at d/city/guangchang — the sect leader from item 1 helpfully teleported next to admin via goto+summon first, same sanctioned admin-positioning shortcut, no stat/skill grant needed since qinfengxia's own base combat stats were already sufficient to lose realistically) against qinfengxia (freshly resurrected from the very apprenticeship test above, i.e. not a throwaway) ran a genuine, unmodified multi-round kill-command fight — confirmed no safe-sparring floor applies (kill.lpc has no analogous "stop at half health" logic, matching this session's 10+ other confirmations that kill vs. an NPC is unprotected) — and the character actually died for real, no smash shortcut needed:

你的眼前一黑,接著什么也不知道了....
你死了。
【谣言】某人:我看到沈秋白被流氓头杀死了。真是好惨。
鬼门关 - ...
  白无常(White gargoyle)

Confirms inherit/char/char.lpc's two-stat wound model precisely matches this session's precedent on other libs: eff_qi/eff_jing < 0 (mortal wound) → real die(); qi/jing < 0 alone → unconcious() (non-lethal, revive()s later) — real kill combat clearly reached the former, not just the latter.

Walked the full post-death cycle for real, no NET_DEAD_TIMEOUT-style acceleration needed (the whole sequence is only 5 real-time call_out stages × 5s ≈ 25s):

Standard checklist re-confirm (fast pass, all previously-fixed — no re-derivation needed)

All came back clean, matching the code exactly (not merely assumed from the NOTES history):

Verification method

Native build-debug driver, two concurrent telnet sessions (admin fluffos + test character qinfengxia), debug.log watched continuously via direct file reads after every command batch, not merely spot-checked at the end. Driver killed by exact PID after each restart (two restarts this pass: once to load the chinesed.lpc fix, once implicitly via the initial boot); readlink -f /proc/<pid>/cwd confirmed against this lib's own work/ directory before every kill. qinfengxia's and fluffos's save-file timestamp churn is the only diff left in work/data/ — both ended the session with a clean, in-game quit.

Files modified this pass

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

新角度:扬州醉仙楼购物 + 丐帮左全拜师。2026-07-24 round two 的 list/buy 因没钱被拒,诚实记录「成功购物未现场完成」;2026-08-20 round four 用 qinfengxia 走完余洪兴拜师和 xue,仍没补上一次真正付钱的购买。

实测过程

管理员 fluffos / Mud@2026 / 浮浮。第一输入是「您的英文名字:」(欢迎 画面之后,没有 BIG5 选择)。落地扬州客店 /d/city/kedian。score 「【天神】普通百姓」。log_error() 已有 arning: 闸门,本轮屏幕上没有 编译警告倾泻。

goto /d/city/zuixianlou,list 烤鸡腿八十文铜板 / 牛皮酒袋一两白银 / 包子五十文铜板 / 鲸鱼十两黄金。F_DEALER 里丐帮穷叫化拒绝购买整段是 注释掉的,仍按家族惯例先买再拜。clone /clone/money/gold 后 buy jitui 成功(「你从店小二那里买下了一根烤鸡腿」)。当场 i 还挂着「一两黄金」 + 九十九两白银 + 二十文铜板,跟 xkx2017 一样是 MONEY_D->player_pay() 把金锭 set_amount(0) 后物件还没析构的显示;断线重连后黄金条目消失, 只剩烤鸡腿 + 九十九两白银 + 二十文铜板(10000−80 = 9920),找零数学 正确。

goto /d/gaibang/inhole,左全在树洞里(kungfu/class/gaibang/zuo-quan.lpc, 只检查男性),apprentice zuo 一次成功:左全收徒,score 「丐帮第二十 代弟子」、师父左全。cmds/usr/save.lpc 真正调用两个 save()。user.o 立刻带上 family_name":"丐帮" / master_name":"左全" / generation":20 / class":"beggar"。断线后再连(「重新连线完毕」),称谓/师傅/银子/鸡 腿还在。

本轮没有新的 programming bug。live debug.log 是 libs/xiakexing2017/log/debug.log(Boot Time Fri Sep 4 02:15:41 2026), 无 error: / Too deep recursion。管理员存档未提交。

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

scripts/lpc_fix_negative_ranges.py: 5 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)— tail() simul_efun

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

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

房间基类 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-05)— 巫师留言板存档 data/board/*.o.gz

巫师留言板 wiz_b / towiz_b 以 .o.gz 存档(1996 年的帖子)。本机驱动 restore_object() 优先读 .o.gz,而留言板用 save_object(file) 写明文 .o,所以本机上新帖每次重启都退回旧快照;网页版驱动没有 zlib,只读 .o,而归档自带的 .o 是读不出的二进制。.gz 内容为 GB18030(转码时没被处理),转成 UTF-8 写成 .o,替换归档里那个读不出的二进制 .o;towiz_b 原文有 4 个残字节,转为替换符。删除 .o.gz(KB 01)。用 lpcc 载入留言板核对条数(jqxz2008:wiz_b 47 条、towiz_b 115 条)。

深度功能测试(§10.7,2026-10-06)— 白驼山掌门欧阳锋编译不过

d/baituo/ouyangfeng.lpc 有两处笔误,整个 NPC 从未编译成功:greeting() 里 write("你是谁?我要杀了你!哈 ̄哈 ̄哈 ̄") break; 缺分号,inquiry_map() 里 object me = this - player();(应为 this_player())。白驼山派的掌门因此不存在,拜师、问路(inquiry_map)都无从谈起。两处改正后,新进程里 HEAD 加载失败、工作树加载成功。同一文件在 14 个 xkx 系的库里一字不差。

深度功能测试(§10.7,2026-10-07)— 空的 if (current_water == 0) {}

3 个房间的 valid_leave() 里有一行什么也不做的 if (current_water == 0) {}(编译警告 Expression has no side effects),删去,行为不变:d/xingxiu/nanjiang2.lpc、d/xingxiu/nanjiang3.lpc、d/xingxiu/shanjiao.lpc。

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

scripts/lpc_warnings.py xiakexing2017 --fix 加 scripts/lpc_fix_no_effect.py --apply,共改动 262 个 .lpc 和 10 个 .h(多为删去未用的局部变量)。HEAD 与工作树分别加载(新进程):244 -> 244 通过,无回退;改过的头文件的 67 个包含者 67 -> 67 通过。启动时警告 17 -> 0;新进程登录(输入名字)正常。

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

scripts/toolkit_fix.sh 一轮去掉 13 处多余继承;include/net/ftpdconf.h 把 VALID_READ、VALID_WRITE 定为 1,覆盖 ftpd.h 的 0,先 #undef(同 bmxkx2001、jqxz2008),行为不变。能编译的文件警告清零;改动的 14 个对象在 HEAD 与工作树各载入一次:HEAD PASS 10,tree PASS 10,regress 0。

无门派玩家的 family 映射(2026-10-15,KB 05 §7.30 第三种)

mapping fam = ob->query("family"); 之后直接 fam["family_name"]:没有门派(新人或叛师后)的玩家在学习、练功、拜师、进入门派房间时报 Value being indexed is zero,而不是作者写好的提示。2 处在声明行加上 if (!mapp(fam)) fam = ([ ]);(scripts/lpc_fix_family_guard.py)。

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

另有 3 个文件(绝招、状态、物品、NPC 等,不限于 kungfu/skill)来自 gin/kee/sen/force 体系,同样按 kee→qi、gin/sen→jing、force→neili 改名(scripts/lpc_port_es_props.py --any-file)。

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。5 处补上 if (!mapp(fam)) fam = ([ ]);(刚 recruit_apprentice() 之后的除外)。

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

query_skill() 里的技能名拼错了,或用的是本库改名前的旧名;本库没有这个技能,调用永远得 0:门槛(< N 就拒绝)永远过不去,相关功夫或绝招学不了、用不了;内功互斥(> N 就拒绝)从不生效;公式里这一项一直是 0。经汉字名(提示文字或中文名词典)或纯拼写差异(_/-、少一个连字符、相邻字母颠倒)核对后,改为本库的技能名(scripts/lpc_fix_skill_typo.py):mizongxinfa→mizong-xinfa(longxiang/fanchang)。

family 映射内联取下标(2026-10-15,KB 05 §7.30)

ob->query("family")["master_name"]、me->query("family")["family_name"](泥潭式为 query("family", ob)[...])直接对门派映射取下标:没有门派的人(新玩家、叛师者、大多数 NPC)在 NPC 的打招呼和问话、绝招的门派判断、房间的 valid_leave 里报 Value being indexed is zero,而不是得到拒绝。8 处(7 个文件)改为 dbase 路径查询 query("family/master_name"):有门派时值相同,没有时得 0;原有的判断照常生效(scripts/lpc_fix_family_index.py)。给映射赋值的写法未动。

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

3 个文件、3 处在 create() 里 carry_object("<本库没有的文件>")->wear()(或 wield()、set_amount()):carry_object() 得 0,接着的调用报 Bad argument 1 to EFUN call_other(),NPC 根本载入不了,该有它的房间里就少了这个人。多是从别的系统搬来的 NPC 用了原系统的物品路径(最常见:/d/city/obj/tudao、/clone/cloth/female-cloth、/clone/cloth/cloth)。每处改为 { object carried_ob = carry_object(...); if (carried_ob) carried_ob->wear(); }(scripts/lpc_guard_carry_object.py):NPC 照常出现,只是不带这件东西;文件补回来(或是虚拟对象)照样会带上。载入检查:改动的文件改前能载入 0 个,改后 2 个(2 个原来载入不了),没有退步;其余 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):adm/daemons/network/netmail.lpc、cmds/arch/purge.lpc、d/quanzhou/npc/tang.lpc、d/quanzhou/npc/tang3.lpc、d/taishan/tang3.lpc、include/net/ftpdsupp.h。tang3/tang/meng-zhu 是武林盟主一类的 NPC,原来一个都载入不了;purge 是清除玩家档案的巫师指令;netmail 是网络邮件守护进程(本库缺 mailer.h 等头文件,改后仍载入不了)。改后用 lpcc 逐个载入这些对象:4 个通过,1 个还有别的错误(adm/daemons/network/netmail:/adm/daemons/network/netmail.lpc:17: error: Cannot #include mailer.h)。

水潭里 ging_cost 是 jing_cost 的笔误(2026-10-18)

shuitan 先算 jing_cost,倒水时却扣 receive_damage("jing", ging_cost),ging_cost 从没声明过:Undefined variable 'ging_cost',整个物件载入不了。改成 jing_cost:d/shaolin/obj/shuitan.lpc。改后用 lpcc 逐个载入这些对象:1 个通过。

留言板存档损坏时 setup() 不再抛错(2026-10-18)

data/board/*.o 里有几个是外来的二进制数据(以 ?inh 开头,restore_object() 报 Illegal file format - 1)。inherit/misc/bboard.lpc 的 setup() 里 restore() 直接抛,留言板对象(连带创建它的客店房间,比如 d/city/kedian)载入失败。改成 catch(restore());,留言板以空板启动(xkx2017 的同一个文件早就是这样写的)。上面「存档损坏」那一条里说的“setup() 已经包住了”并不准确。

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 载入不了,刷出它的房间也少一个人。本库有 10 个对象设了本库没有文件的武功(3 个名字,最多的是 xuanyuan-arrow、liangyi-jian、jinwu-daofa)。三处都改成 return:这一门武功不给、不映射,对象照常载入,只是少了它(缺的武功本身仍是内容缺口)。抽样载入这些对象:10 个里 10 个通过。(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 映射不到,门派弟子用普通轻功/拳脚)。按核对过的对照表改正,共 6 处、5 个文件:xinxiu-duzhang→xingxiu-duzhang(2 处)、tiaji-jian→taiji-jian(2 处)、jinyin-baiguzhao→jiuyin-baiguzhao(1 处)、qishang--quan→qishang-quan(1 处)。(scripts/lpc_fix_skill_names.py)

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

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

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

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

玩家打造兵器模板没有打造者时(2026-10-19,KB 06 §7.275)

/d/npc/m_weapon/weapon/m_blade.lpc、/d/npc/m_weapon/weapon/m_club.lpc、/d/npc/m_weapon/weapon/m_staff.lpc、/d/npc/m_weapon/weapon/m_sword.lpc、/d/npc/m_weapon/weapon/m_whip.lpc(共 5 个)的 create() 开头 me = this_player(); 之后无条件读 me->query("weapon/name") 等打造数据。房间刷新时没有人在场(me 为 0),或者 NPC 在别的 NPC 的命令环境里 carry_object() 这个模板(NPC 的 weapon 属性是文件名字符串),旧驱动里读出来是 0 或字符串,switch 照样落到 default;新驱动报 Bad argument 1 to EFUN call_other() / Bad argument 1 to switch,NPC 和它所在的房间载入不了(--walk 普查里一个玩家走进房间就触发)。在 //读入数据 之后加 if (!me || !mapp(me->query("weapon"))) { setup(); return; }:没有打造者就留一个没有名字的空模板,玩家打造的流程不变。

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

转换时丢掉的分号、逗号、引号或文件末尾的 },共 3 个文件,修前 HEAD 载入失败。别的库里同路径的完好副本只比这里少几个标点,按它补回(括号里是那个库):/d/baituo/npc/obj/bowl.lpc(cctx)、/d/baituo/obj/bowl.lpc(cctx)、/d/city2/npc/zhengmen.lpc(jqxz2008)。修后与 HEAD 逐个对比载入:这 3 个物件全部载入,没有退步(scripts/lpc_restore_from_twin.py、lpc_fix_eaten_quote.py --comma、lpc_fix_eof_braces.py)。

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)。改动:/d/mingjiao/shanmen.lpc(1 处)。

行编辑器指令编译失败:(: this_object(), ({ "do_to", me, arg }) :)(2026-10-20,KB 06 §7.284)

Illegal to use local variable in functional.:call_other 形式的函数指针 (: ob, ({ "函数", 参数 }) :) 的数组里只能是常量,写了局部变量 me / arg 整个指令就编译不过,to 这类先开编辑器、再回调自己的指令因此从未能用。改成 (: call_other, this_object(), "do_to", me, arg :)(参数在建立指针时绑定,编辑器再把输入的文字接在最后,do_to(me, arg, text) 收到全文;scripts/lpc_fix_functional_locals.py)。改动:/cmds/usr/to.lpc(1 处)、/cmds/wiz/to.lpc(1 处)。

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

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

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

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

翻页器 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)。改动:/feature/more.lpc。

存档里的乱码文件:restore_object() 报 Illegal file format(2026-10-20,KB 06 §7.316)

存档里有一批数据文件是乱码:开头是 ?adm、?inh、?clo、?obj(#/adm/… 的第一个字节被换成了 ?),后面是随机字节,没有任何驱动读得了。adm/daemons/chinesed 在预载时 restore() 读 data/e2c_dict.o,首次登录时起始房间的留言板 /clone/board/kedian_b 读 data/board/*_b.o;restore_object() 对不是文本的文件抛 Illegal file format,调用处的 catch()(KB 06 §7.255)保住了房间和精灵,但本库 master 的 error_handler() 把调用栈返回给驱动,每次启动和首次登录都在控制台打印一次或两次 错误讯息被拦截。现在共用的 feature/save.lpc 的 restore() 先读文件第一个字节,不是 # 就返回 0(读被 master 拒绝时 read_bytes() 返回 0,仍由 restore_object() 决定,和以前一样);留言板和字典从空开始,第一次 save() 写出合法文件,乱码文件原样留在树里。实测:启动和登录都没有报错,已保存的角色重新连线照常加载。

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 个文件)。

存档里的 .c 文件名改成 .lpc(2026-10-21,KB 03 §4.2 第 9 项)

存档里写着 "/d/city/npc/wei.c" 这样的文件名;extract 时源文件已经全部改成 .lpc,而驱动对带扩展名的路径只认字面,new() / load_object() / move() 都找不到它,调用方多半把失败当成「没有这个东西」悄悄跳过。本库 3 个存档共 6 处按 scripts/lpc_fix_save_dotc.py 改成 .lpc(只改 .lpc 存在而 .c 不存在的字符串;玩家存档、备份、留言板、巫师目录和文件索引缓存不动)。改动:/d/shaolin/npc/obj/kat.o(1 处)、/d/shaolin/obj/kat.o(1 处)、/d/shenlong/obj/lxlx.o(4 处)(共 3 个存档)。

巫师命令不带参数时对 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)。改动(1 个文件,括号里是接住 0 的 efun):/cmds/wiz/test.lpc(present)。加载检查 HEAD 与工作树对照,没有退步。

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 取回并删除)。改动(2 个文件):/cmds/adm/recover.lpc、/cmds/arch/blockade.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.,同一个动词的其他处理函数不再尝试(不是最新的一条就悄悄跳过)。删除了 15 条注册(15 个文件,scripts/lpc_fix_dead_add_action.py),动词留给别的处理函数:/d/mingjiao/npc/changjinpeng.lpc(do_join)、/d/mingjiao/npc/lengqian.lpc(do_join)、/d/mingjiao/npc/pengyingyu.lpc(do_join)、/d/mingjiao/npc/shuobude.lpc(do_join)、/d/mingjiao/npc/tangyang.lpc(do_join)、/d/mingjiao/npc/wensong.lpc(do_join)、/d/mingjiao/npc/yanyuan.lpc(do_join)、/d/mingjiao/npc/yinwufu.lpc(do_join)、/d/mingjiao/npc/yinwulu.lpc(do_join)、/d/mingjiao/npc/yinwushou.lpc(do_join) 等共 15 个文件。最多的函数名:do_join 13 处、do_list 1 处、do_qiecuo 1 处。

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

__DIR__ 是当前文件所在目录(带结尾斜杠),__DIR__ "../obj/x" 就是 /d/zhiye/npc/../obj/x;驱动的 legal_path() 不接受带 .. 的路径,new() / load_object() 直接抛 *Read access denied.(实测,find_object() 对它答 0)。走库自己的 carry_object() / 房间 objects 表的路径,库的基类会先规范化,能用;直接把路径交给 new() / clone_object() / load_object() / move() 的不行,调用到那一步才报错。改成规范化后的绝对路径(scripts/lpc_fix_dotdot_dir.py --direct):/d/wudang/npc/xiaocui.lpc(__DIR__ "../obj/nuerxiang" → "/d/wudang/obj/nuerxiang")。

汉字按字节切片的老写法没有换成按字符:改法名 "澄" + 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] == "澄")永远不成立。

本库 21 处(13 个文件):拼名字的赋值 13 处、与汉字常量比较 8 处。例:name[0..1] → name[0..0](/kungfu/class/emei/kong.lpc:134);name[2..3] → name[1..1](/kungfu/class/shaolin/cheng.h:157);name[0..1] == "慧" → name[0..0] == "慧"(/kungfu/class/shaolin/cheng.h:140)。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,不要重跑。

黑木崖脚下的老者:init() 里留着调试语句,每个走进 /d/heimuya/road1 的人都触发运行时错误(2026-10-22,KB 06 §7.361)

d/heimuya/npc/laozhe.lpc 的 init() 在 ::init() 之后有 what = new(__DIR__ "obj/yancao"); sprintf("%s aaa\n", what);:调试遗留,%s 收字符串或数字、不收对象,于是 (s)printf(): Incorrect argument to type %s. (arg: 0) 每次有人进入乱石坡都抛一次,后面的 call_out("greeting", ...) 永远不执行,老者不再搭话(还每次白克隆一个烟草)。两行都注释掉(同系的 haiyang2 / hy / hy5 / cctx / fqyy2 早就注释了)。证明:scripts/lpc_reach_census.py xiakexing2017 --load --walk(一个普通 NPC 访客走进每个加载成功的房间):HEAD 336/337,改后 337/337。

按字节写的汉字循环、茶馆猜诗和同盟频道标题(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 区间 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(一格两个空格)。本库 4 处(2 个文件)。证明(lpcc_eval.py hhsj):tribar_graph(6,9,12) 在 HEAD 画出整条 12 个■,改后为 6 个■+3 个□;麻将 strlen(tempB)/2 是两字符牌码,未改。

doc/build devil 丢分号/粘连 inherit(2026-10-27,KB 04 §6.25 Also)

doc/build/devil.lpc(建档示例倭寇)被格式化弄坏:inherit NPC void create() 粘在一起,语句之间的 ; 全丢,还有嵌套坏掉的 set("con", set("con", 10)。按原意重写成带 setup() 的完好 NPC(原 /obj/blade、/obj/cloth 在多数库不存在,示例里去掉携带以免 create 炸)。证明:HEAD 加载失败;改后加载成功。